Skip to content

Commit

Permalink
add the ability to use custom references for validating rules, ref le…
Browse files Browse the repository at this point in the history
…ss#413, document JS API, fixes less#479, update man page
  • Loading branch information
nebulon42 committed Jul 9, 2017
1 parent b0a03fa commit abd3d94
Show file tree
Hide file tree
Showing 19 changed files with 487 additions and 147 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ for Mapnik XML and `json` for the JSON variant (part of [#413](https://github.co
For Mapnik XML all character data as tag content is now prefixed with CDATA.
* Expressions are allowed in filters e.g. `[height] % 50 = 0` or `[height] + 10 = 0`, fields have to be properly written within brackets
([#377](https://github.com/mapbox/carto/issues/377))
* carto now accepts custom references for validating rules (part of [#413](https://github.com/mapbox/carto/issues/413))
* The JavaScript API has been documented ([#479](https://github.com/mapbox/carto/issues/479))

### Breaking changes

Expand All @@ -33,6 +35,13 @@ string. `data` contains the output as before and `msg` now contains an array of
In case of errors `data` is `null`.
* carto now only throws errors in case of program failures. All other style processing
related errors can be found in the `msg` property (see above).
* The constructor of `carto.Renderer` now only takes one options object
instead of a `env` and `options` parameter.
* The option `mapnik_version` and `validation_data` of `carto.Renderer` have
been renamed to `version` and `validationData` respectively.
* `carto.Renderer` now checks each option before forwarding them to the rendering
chain. Supported options are defined in the documentation. If you use a option that is
missing please open a issue.

## 0.18.1

Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,13 @@ The `Renderer` interface is the main API for developers, and it takes an MML fil

try {
var data = fs.readFileSync(input, 'utf-8');
var mml = new carto.MML();
var mml = new carto.MML({});
mml.load(path.dirname(input), data, function (err, data) {
var output = {};

if (!err) {
output = new carto.Renderer({
filename: input,
local_data_dir: path.dirname(input),
filename: input
}).render(data);
}

Expand Down
16 changes: 3 additions & 13 deletions bin/carto
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
var path = require('path'),
fs = require('fs'),
carto = require('../lib/carto'),
semver = require('semver'),
url = require('url'),
_ = require('lodash'),
util = require('../lib/carto/util');
Expand All @@ -17,7 +16,7 @@ var yargs = require('yargs')
.options('b', {alias:'benchmark', boolean:true, describe:'Outputs total compile time'})
.options('l', {alias:'localize', boolean:true, default:false, describe:'Use millstone to localize resources when loading an MML'})
.options('n', {alias:'nosymlink', boolean:true, describe:'Use absolute paths instead of symlinking files'})
.options('a', {alias:'api', describe:'Specify Mapnik API version', default:carto.tree.Reference.getLatest()})
.options('a', {alias:'api', describe:'Specify Mapnik API version'})
.options('f', {alias:'file', describe:'Outputs to the given file instead of stdout.'})
.options('o', {alias:'output', describe:'Specify output format (mapnik, json)', default:'mapnik'})
.options('q', {alias:'quiet', boolean:true, default:false, describe:'Do not output any warnings'})
Expand Down Expand Up @@ -45,13 +44,6 @@ if (!input) {
process.exit(1);
}

if (options.api) {
if (!semver.valid(options.api)) {
console.error("carto: invalid Mapnik API version. A valid version is e.g. 3.0.0 or 3.0.10");
process.exit(1);
}
}

if (options.benchmark) {
var start = +new Date;
}
Expand Down Expand Up @@ -96,10 +88,8 @@ function compile(err, data) {
benchmark: options.benchmark,
ppi: options.ppi,
quiet: options.quiet,
outputFormat: options.output
},
{
mapnik_version: options.api
outputFormat: options.output,
version: options.api
});
try {
var output;
Expand Down
182 changes: 175 additions & 7 deletions docs/installation_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ Optionally you may also want to install millstone which is required for resolvin
Having *millstone* installed specifically enables support for localizing external resources (URLs and local files) referenced in your MML file,
and detecting projections (using `node-srs <https://github.com/mapbox/node-srs>`_).

Usage
=====
Usage from the command line
===========================

Now that Carto is installed you should have a *carto* command line tool available that can be run on a CartoCSS project::

Expand Down Expand Up @@ -61,24 +61,27 @@ The following command line options are available:
-v / --version
Display version information

Usage from JavaScript (Carto API)
=================================

Alternatively, Carto can be used from JavaScript. The *Renderer* interface is the main API for developers,
and it takes an MML file as a string as input. ::
Alternatively, Carto can be used from JavaScript. While you theoretically could
instantiate almost all of the classes the main outward facing stable interfaces
are the ``Renderer`` and the ``MML`` interface. We start with an example and describe
the details afterwards::

// defined variables:
// - input (the name or identifier of the file being parsed)
var carto = require('carto');

try {
var data = fs.readFileSync(input, 'utf-8');
var mml = new carto.MML();
var mml = new carto.MML({});
mml.load(path.dirname(input), data, function (err, data) {
var output = {};

if (!err) {
output = new carto.Renderer({
filename: input,
local_data_dir: path.dirname(input),
filename: input
}).render(data);
}

Expand All @@ -103,5 +106,170 @@ and it takes an MML file as a string as input. ::
...
}

MML
---

The MML interface loads and processes a MML file (see :ref:`mml-file-structure` for details).
You instantiate the class with ``carto.MML``. The constructor takes a options object with
the following possible attributes:

* ``localize`` *boolean* (same as -l / --localize on the command line) - this uses
`millstone <https://github.com/tilemill-project/millstone>`_ to localize stylesheet resources
* ``nosymlink`` *boolean* (same as -n / --nosymlink on the command line) - for millstone, tells
it to use unmodified paths instead of symlinking files

By calling ``load(basedir, data, callback)`` the MML file is loaded and processed.
This method does not perform reading from a file, so you have to read the contents
of the file yourself and provide it as string to the method via the ``data`` parameter to the
load function. The ``basedir`` parameter is used to resolve stylesheet references.
When the processing is finished the specified ``callback`` function is called, which
has the following signature::

function (err, data) {}

If an error occurred you find the message within ``err`` and ``data`` is ``null``.
When successful you find the processed MML data structure in ``data`` and ``err`` is ``null``.
The structure within ``data`` is excpected by the ``Renderer`` interface's ``render`` method.

.. note:: If you want to use Carto within the browser you should not use MML loading via ``carto.MML.load``.
Instead you should supply the JSON of the MML including the Stylesheet strings directly to ``carto.Renderer.render``.

Renderer
--------

The Renderer interface performs the parsing and transformation for rendering from
a MML file string (either self loaded or loaded through the MML interface) or from a MSS
file string (without layers). You instantiate the class with ``carto.Renderer``.
The constructor takes a options object with the following possible attributes:

* ``benchmark`` *boolean* (similar to -b / --benchmark on the command line) - specifies
if carto should run in benchmarking mode
* ``effects`` *array* - a container for side-effects limited to FontSets
* ``filename`` *string* - name of the input file, used to format errors and warnings
* ``outputFormat`` *string [mapnik|json]* (similar to -o / --output on the command line)
- specifies which format the output should have, either Mapnik XML or JSON similar to Mapnik XML
* ``ppi`` *float* (similar to -ppi on the command line) - Pixels per inch used to convert m, mm, cm, in, pt, pc to pixels
* ``quiet`` *boolean* (similar to -q / --quiet on the command line) - if carto should output
warnings or not
* ``reference`` *class* - carto uses a reference to validate input. You can specify your own
which has to adhere to the specification. (see :ref:`reference`)
* ``validationData`` *object*

* ``fonts`` *array* - a list of fonts that carto should use to validate if used fonts are valid/present

* ``version`` *string (semver)* (similar to -a / --api on the command line) - specify which
Mapnik API version carto should use

``carto.Renderer`` offers two methods for actual rendering. You can either use ``render(data)`` or
``renderMSS(data)``. Both accept a string of either a processed MML file or a MSS style fragment.
The ``render`` method produces a full-featured style output while the ``renderMSS`` outputs only
a style fragment. Both return the following object::

{
msg: [],
data: ''
}

If errors or warnings occurred during rendering you will find them in ``msg`` and ``data``
will be ``null`` (in case of errors). The actual output is found in ``data`` if no errors
occurred.

Util
----

Carto provides a Util class to assist you with e.g. message formatting. Like in the
example you can call ``getMessageToPrint`` with a received message object to output it
nicely formatted as string.

.. _reference:

Using a custom reference
------------------------

Carto uses a reference to validate input. This reference specifies which rules and functions
are valid and which types a rule can take. It also describes how rules are transformed for
the output. By default carto uses `mapnik-reference <https://github.com/mapnik/mapnik-reference>`_
as reference, but you can also use your own. It has to adhere to the following specification::

{
versions: [], // array of versions (semver) as strings
latest: '', // latest version (semver) as strings
load: function (version) {} // return data structure for specified version
}

The data structure returned by ``load`` has to look like this::

{
version: '', // version (semver) as string
style: {}, // rules that apply to the style as a whole
layer: {}, // rules that apply to a layer as a whole
symbolizers: {}, // rules that apply to different elements of the renderer, this elements make up the map
colors: {}, // color names and their mapping to RGB values
datasources: {} // possible data sources for the rendering library and their parameters
}

.. note:: ``datasources`` is not yet used by carto for validation.

All entries that contain rules are objects where there attributes are named after
a color, symbolizer or rule. ``style`` and ``layer`` have the same inner structure.
Here is an example::

{
'filter-mode': {
type: {},
doc: '',
'default-value': '',
'default-meaning': ''
}
...
}

``symbolizer`` first contains the possible symbolizers and then their rules::

{
polygon: {
fill: {},
'fill-opacity': {}
...
}
...
}

``colors`` maps color names to their RGB values::

{
aliceblue: [ 240, 248, 255 ],
antiquewhite: [ 250, 235, 215 ]
...
}

``datasources`` is similar to ``symbolizers`` and contains first the possible data sources
and then their possible parameters::

{
csv: {
file: {},
base: {}
...
}
}

Rules (all the parts that where specified with ``{}`` with a little preview at ``filter-mode``)
can have several attributes that are evaluated::

name: {
css: '', // rule name which is used in CartoCSS
default-meaning: '', // meaning of the default value
default-value: '', // default value of the rule
doc: '', // documentation about the rule
expression: bool, // whether this rule is a expression or not
functions: [], // array of arrays that contain function name and # of params e.g. ["matrix", 6]
range: '', // range of values that are allowed e.g. 0-1
required: bool, // if this rule is required
status: '[unstable|experimental|deprecated]', // if omitted it means stable
type: '[bbox|boolean|color|float|functions|numbers|string|uri]', // type can also be an array of keywords
}

.. caution:: Adherence to the specification is not assessed in-depth because that would
be too resource intensive. If you don't adhere to the specification it is quite likely
that you hit runtime errors.
2 changes: 2 additions & 0 deletions docs/mml.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _mml-file-structure:

MML File Structure
==================

Expand Down
6 changes: 3 additions & 3 deletions lib/carto/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -447,8 +447,8 @@ carto.Parser = function Parser(env) {

keywordcolor: function() {
var rgb = chunks[j].match(/^[a-z]+/);
if (rgb && rgb[0] in tree.Reference.data.colors) {
var data = tree.Reference.data.colors[$(/^[a-z]+/)];
if (rgb && rgb[0] in that.env.ref.data.colors) {
var data = that.env.ref.data.colors[$(/^[a-z]+/)];
var a = 1;
if (data.length > 3) {
a = data[3];
Expand Down Expand Up @@ -670,7 +670,7 @@ carto.Parser = function Parser(env) {
value = $(this.value);

if (value && $(this.end)) {
return new tree.Rule(name, value, memo, env.filename);
return new tree.Rule(env.ref, name, value, memo, env.filename);
} else {
restore();
}
Expand Down
Loading

0 comments on commit abd3d94

Please sign in to comment.