aboutsummaryrefslogtreecommitdiffstats
path: root/generate-elm.js
diff options
context:
space:
mode:
Diffstat (limited to 'generate-elm.js')
-rw-r--r--generate-elm.js325
1 files changed, 213 insertions, 112 deletions
diff --git a/generate-elm.js b/generate-elm.js
index 1643428..7207f4e 100644
--- a/generate-elm.js
+++ b/generate-elm.js
@@ -5,39 +5,44 @@ function generateProperties(spec) {
var docs = {};
var enums = {};
layouts.forEach(l => {
- const layerType = titleCase(l.split('_')[1])
+ const layerType = titleCase(l.split("_")[1]);
docs[layerType] = [];
codes[layerType] = [];
Object.entries(spec[l]).forEach(([name, prop]) => {
- if (name == 'visibility') return '';
- if (prop.type === 'enum') {
- enums[name] = Object.keys(prop.values).join(' | ');
+ if (name == "visibility") return "";
+ if (prop.type === "enum") {
+ enums[name] = Object.keys(prop.values).join(" | ");
}
- codes[layerType].push(generateElmProperty(name, prop, layerType, 'Layout'));
+ codes[layerType].push(
+ generateElmProperty(name, prop, layerType, "Layout")
+ );
docs[layerType].push(camelCase(name));
- })
- })
+ });
+ });
paints.forEach(l => {
- const layerType = titleCase(l.split('_')[1])
+ const layerType = titleCase(l.split("_")[1]);
Object.entries(spec[l]).forEach(([name, prop]) => {
- if (name == 'visibility') return '';
- if (prop.type === 'enum') {
- enums[name] = Object.keys(prop.values).join(' | ');
+ if (name == "visibility") return "";
+ if (prop.type === "enum") {
+ enums[name] = Object.keys(prop.values).join(" | ");
}
- codes[layerType].push(generateElmProperty(name, prop, layerType, 'Paint'))
+ codes[layerType].push(
+ generateElmProperty(name, prop, layerType, "Paint")
+ );
docs[layerType].push(camelCase(name));
- })
- })
- Object.values(docs).forEach(d => d.sort())
+ });
+ });
+ Object.values(docs).forEach(d => d.sort());
Object.values(codes).forEach(d => d.sort());
- console.log(enums);
return `
module Mapbox.Layer exposing (
Layer, SourceId, Background, Fill, Symbol, Line, Raster, Circle, FillExtrusion, Heatmap, Hillshade, LayerAttr,
encode,
background, fill, symbol, line, raster, circle, fillExtrusion, heatmap, hillshade,
- metadata, source, sourceLayer, minzoom, maxzoom, filter, visible,
- ${Object.values(docs).map(d => d.join(', ')).join(',\n ')})
+ metadata, sourceLayer, minzoom, maxzoom, filter, visible,
+ ${Object.values(docs)
+ .map(d => d.join(", "))
+ .join(",\n ")})
{-|
Layers specify what is actually rendered on the map and are rendered in order.
@@ -62,15 +67,19 @@ Paint properties are applied later in the rendering process. Changes to a paint
### General Attributes
@docs LayerAttr
-@docs metadata, source, sourceLayer, minzoom, maxzoom, filter, visible
-
-${Object.entries(docs).map(([section, docs]) => `### ${section} Attributes\n\n@docs ${docs.join(', ')}`).join('\n\n')}
+@docs metadata, sourceLayer, minzoom, maxzoom, filter, visible
+
+${Object.entries(docs)
+ .map(
+ ([section, docs]) =>
+ `### ${section} Attributes\n\n@docs ${docs.join(", ")}`
+ )
+ .join("\n\n")}
-}
import Array exposing (Array)
-import Json.Decode
import Json.Encode as Encode exposing (Value)
-import Mapbox.Expression as Expression exposing (Anchor, CameraExpression, Color, DataExpression, Expression, LineJoin)
+import Mapbox.Expression as Expression exposing (Anchor, Auto, CameraExpression, Color, DataExpression, Expression, LineCap, LineJoin, Position, RasterResampling, SymbolPlacement, TextFit, TextJustify, TextTransform, FormattedText)
{-| Represents a layer. -}
type Layer
@@ -121,9 +130,7 @@ encode (Layer value) =
value
-
-
-layerImpl tipe source id attrs =
+layerImpl tipe id source attrs =
[ ( "type", Encode.string tipe )
, ( "id", Encode.string id )
, ( "source", Encode.string source)
@@ -137,16 +144,16 @@ encodeAttrs attrs =
let
{ top, layout, paint } =
List.foldl
- (\\attr ({ top, layout, paint } as lists) ->
+ (\\attr lists ->
case attr of
Top key val ->
- { lists | top = ( key, val ) :: top }
+ { lists | top = ( key, val ) :: lists.top }
Paint key val ->
- { lists | paint = ( key, val ) :: paint }
+ { lists | paint = ( key, val ) :: lists.paint }
Layout key val ->
- { lists | layout = ( key, val ) :: layout }
+ { lists | layout = ( key, val ) :: lists.layout }
)
{ top = [], layout = [], paint = [] }
attrs
@@ -155,7 +162,7 @@ encodeAttrs attrs =
{-| The background color or pattern of the map. -}
background : String -> List (LayerAttr Background) -> Layer
-background tipe id attrs =
+background id attrs =
[ ( "type", Encode.string "background" )
, ( "id", Encode.string id )
]
@@ -244,116 +251,210 @@ visible : Expression CameraExpression Bool -> LayerAttr any
visible vis =
Layout "visibility" <| Expression.encode <| Expression.ifElse vis (Expression.str "visible") (Expression.str "none")
-${Object.entries(codes).map(([section, codes]) => `-- ${section}\n\n${codes.join('\n')}`).join('\n\n')}
-`
+${Object.entries(codes)
+ .map(([section, codes]) => `-- ${section}\n\n${codes.join("\n")}`)
+ .join("\n\n")}
+`;
+}
+
+function codeSnippet(name, type) {
+ return "`" + camelCase(name, type) + "`";
}
function requires(req) {
- if (typeof req === 'string') {
- return `Requires \`${camelCase(req)}\`.`;
- } else if (req['!']) {
- return `Disabled by \`${camelCase(req['!'])}\`.`;
- } else if (req['<=']) {
- return `Must be less than or equal to \`${camelCase(req['<='])}\`.`;
+ if (typeof req === "string") {
+ return `Requires ${codeSnippet(req)}.`;
+ } else if (req["!"]) {
+ return `Disabled by ${codeSnippet(req["!"])}.`;
+ } else if (req["<="]) {
+ return `Must be less than or equal to \`${camelCase(req["<="])}\`.`;
} else {
- const [name, value] = Object.entries(req)[0];
- if (Array.isArray(value)) {
- return `Requires \`${camelCase(name)}\` to be ${
- value
- .reduce((prev, curr) => [prev, ', or ', curr])}.`;
- } else {
- return `Requires \`${camelCase(name)}\` to be \`${value}\`.`;
- }
+ const [name, value] = Object.entries(req)[0];
+ if (Array.isArray(value)) {
+ return `Requires ${codeSnippet(name)} to be ${value.slice(1).reduce(
+ (prev, curr) => prev + ", or " + codeSnippet(curr), codeSnippet(value[0])
+ )}.`;
+ } else {
+ return `Requires ${codeSnippet(name)} to be \`${value}\`.`;
+ }
}
}
function generateElmProperty(name, prop, layerType, position) {
- if (name == 'visibility') return ''
- if (prop['property-type'] === 'constant') throw "Constant property type not supported";
+ if (name == "visibility") return "";
+ if (prop["property-type"] === "constant")
+ throw "Constant property type not supported";
const elmName = camelCase(name);
- const exprKind = prop['sdk-support']['data-driven styling'] && prop['sdk-support']['data-driven styling'].js ? 'any' : 'CameraExpression';
+ const exprKind =
+ prop["sdk-support"]["data-driven styling"] &&
+ prop["sdk-support"]["data-driven styling"].js
+ ? "any"
+ : "CameraExpression";
const exprType = getElmType(prop);
- let bounds = '';
- if ('minimum' in prop && 'maximum' in prop) {
- bounds = `\n\nShould be between \`${prop.minimum}\` and \`${prop.maximum}\` inclusive. `
- } else if ('minimum' in prop) {
- bounds = `\n\nShould be greater than or equal to \`${prop.minimum}\`. `
- } else if ('maximum' in prop) {
- bounds = `\n\nShould be less than or equal to \`${prop.maximum}\`. `
+ let bounds = "";
+ if ("minimum" in prop && "maximum" in prop) {
+ bounds = `\n\nShould be between \`${prop.minimum}\` and \`${
+ prop.maximum
+ }\` inclusive. `;
+ } else if ("minimum" in prop) {
+ bounds = `\n\nShould be greater than or equal to \`${prop.minimum}\`. `;
+ } else if ("maximum" in prop) {
+ bounds = `\n\nShould be less than or equal to \`${prop.maximum}\`. `;
+ }
+ let valueHelp = "";
+ if (exprType == "(Anchor Never)" || exprType == "(Anchor Auto)") {
+ valueHelp = Object.entries(prop.values)
+ .map(
+ ([value, { doc }]) =>
+ `\n- ${codeSnippet(value, exprType)}: ${docify(doc)}`
+ )
+ .join("");
}
return `
-{-| ${prop.doc.replace(/`(\w+\-.+?)`/g, str => '`' + camelCase(str.substr(1)))} ${position} property. ${bounds}${prop.units ? `\nUnits in ${prop.units}. ` : ''}${prop.default !== undefined ? 'Defaults to `' + prop.default + '`. ' : ''}${prop.requires ? prop.requires.map(requires).join(' ') : ''}
+{-| ${docify(prop.doc, elmName)} ${position} property. ${bounds}${
+ prop.units ? `\nUnits in ${prop.units}. ` : ""
+ }${prop.default !== undefined && elmName !== 'heatmapColor' && elmName !== 'textFont' ? "Defaults to `" + camelCase(prop.default.toString(), exprType) + "`. " : ""}${
+ prop.requires ? prop.requires.map(requires).join(" ") : ""
+ }${valueHelp}
-}
${elmName} : Expression ${exprKind} ${exprType} -> LayerAttr ${layerType}
${elmName} =
- Expression.encode >> ${position} "${name}"`
+ Expression.encode >> ${position} "${name}"`;
+}
+
+const enumMap = {
+ "map | viewport | auto": "(Anchor Auto)",
+ "map | viewport": "(Anchor Never)",
+ "left | center | right": "TextJustify",
+ "center | left | right | top | bottom | top-left | top-right | bottom-left | bottom-right":
+ "Position",
+ "none | width | height | both": "TextFit",
+ "butt | round | square": "LineCap",
+ "bevel | round | miter": "LineJoin",
+ "point | line | line-center": "SymbolPlacement",
+ "none | uppercase | lowercase": "TextTransform",
+ "linear | nearest": "RasterResampling"
+};
+
+const flatEnumMap = Object.assign(Object.entries(enumMap).reduce((res, [values, tipe]) => values.split(' | ').reduce((obj, value) => Object.assign({}, obj, {[value]: tipe}), res), {}), {
+ map: 'Anchor',
+ viewport: 'Anchor',
+ auto: "Anchor"
+});
+
+const reverseEnumMap = Object.entries(enumMap).reduce((res, [values, tipe]) => tipe ==="(Anchor Auto)" || tipe === "(Anchor Never)" ? res : Object.assign({}, res, {[tipe]: values.split(' | ')}), {})
+
+console.error(reverseEnumMap)
+
+function docify(str, name) {
+ switch (name) {
+ case "lineGradient":
+ case "lineDasharray":
+ return str;
+ case "heatmapColor":
+ return `Defines the color of each pixel based on its density value in a heatmap. The value should be an Expression that uses \`heatmapDensity\` as input. Defaults to:
+
+ E.heatmapDensity
+ |> E.interpolate E.Linear
+ [ (0.0, rgba 0 0 255 0)
+ , (0.1, rgba 65 105 225 1)
+ , (0.3, rgba 0 255 255 1)
+ , (0.5, rgba 0 255 0 1)
+ , (0.7, rgba 255 255 0 1)
+ , (1.0, rgba 255 0 0 1)]`
+ case "textField":
+ return `Value to use for a text label.`;
+ }
+ ///`(\w+\-.+?)`/g
+ return str.replace(
+ /`(.+?)`/g,
+ (str, match) => "`" + camelCase(match) + "`"
+ )
}
-function getElmType({type, value, values}) {
- switch(type) {
- case 'number':
- return 'Float';
- case 'boolean':
+function getElmType({ type, value, values }) {
+ switch (type) {
+ case "number":
+ return "Float";
+ case "boolean":
return "Bool";
- case 'string':
- return 'String';
- case 'color':
- return 'Color';
- case 'array':
- switch(value) {
- case 'number':
- return '(Array Float)';
- case 'string':
- return '(Array String)';
- }
- case 'enum':
- switch(Object.keys(values).join(' | ')) {
- case "map | viewport":
- return 'Anchor';
- case "map | viewport | auto":
- return 'AnchorAuto';
- case "center | left | right | top | bottom | top-left | top-right | bottom-left | bottom-right":
- return 'Position';
- case 'none | width | height | both':
- return 'TextFit';
- case 'butt | round | square':
- return 'LineCap';
- case 'bevel | round | miter':
- return 'LineJoin';
- case 'point | line':
- return 'SymbolPlacement';
- case 'left | center | right':
- return 'TextJustify';
- case 'none | uppercase | lowercase':
- return 'TextTransform';
+ case "string":
+ return "String";
+ case "color":
+ return "Color";
+ case "array":
+ switch (value) {
+ case "number":
+ return "(Array Float)";
+ case "string":
+ return "(Array String)";
}
+ case "formatted":
+ return "FormattedText";
+ case "enum":
+ const res = enumMap[Object.keys(values).join(" | ")];
+ if (res) return res;
}
- throw `Unknown type ${type}`
+ throw `Unknown type ${type}, ${value}, ${values && Object.keys(values)}`;
}
function titleCase(str) {
- return str.replace(/\-/, ' ').replace(
- /\w\S*/g,
- function(txt) {
- return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
- }
- ).replace(/\s/, '');
- }
-
-function camelCase(str) {
- return str.replace(/(?:^\w|[A-Z]|\b\w|\-\w)/g, function(letter, index) {
- return index == 0 ? letter.toLowerCase() : letter.toUpperCase();
- }).replace(/(?:\s|\-)+/g, '');
+ return str
+ .replace(/\-/, " ")
+ .replace(/\w\S*/g, function(txt) {
+ return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
+ })
+ .replace(/\s/, "");
}
+function camelCase(str, type) {
+ if (type && reverseEnumMap[type]) {
+ str = type + ' ' + str;
+ } else if (flatEnumMap[str]) {
+ str = flatEnumMap[str] + ' ' + str;
+ } else if (str === "rgba(0, 0, 0, 0)") {
+ return "rgba 0 0 0 0"
+ }
+ return str
+ .replace(/(?:^\w|[A-Z]|\b\w|\-\w)/g, function(letter, index) {
+ return index == 0 ? letter.toLowerCase() : letter.toUpperCase();
+ })
+ .replace(/(?:\s|\-)+/g, "");
+}
function makeSignatures(name, constants) {
return `{-| -}
type ${name} = ${name}
- ${constants.split(' | ').map(c => `
+ ${constants
+ .split(" | ")
+ .map(
+ c => `
{-| -}
-${camelCase(name + ' ' + c)} : Expression exprType ${name}
-${camelCase(name + ' ' + c)} = Expression (Json.Encode.string "${c}")
-`).join('\n')}`
+${camelCase(name + " " + c)} : Expression exprType ${name}
+${camelCase(name + " " + c)} = Expression (Json.Encode.string "${c}")
+`
+ )
+ .join("\n")}`;
}
+
+/// --- NODEJS STUFF
+
+var stdin = process.stdin,
+ stdout = process.stdout,
+ inputChunks = [];
+
+stdin.resume();
+stdin.setEncoding("utf8");
+
+stdin.on("data", function(chunk) {
+ inputChunks.push(chunk);
+});
+
+stdin.on("end", function() {
+ var inputJSON = inputChunks.join(""),
+ parsedData = JSON.parse(inputJSON),
+ output = generateProperties(parsedData);
+ stdout.write(output);
+ stdout.write("\n");
+});