{"id":17269489,"url":"https://github.com/systemed/glug","last_synced_at":"2025-04-14T08:22:16.870Z","repository":{"id":45791649,"uuid":"43906846","full_name":"systemed/glug","owner":"systemed","description":"Text-based markup for Mapbox GL.","archived":false,"fork":false,"pushed_at":"2023-01-05T17:43:24.000Z","size":46,"stargazers_count":39,"open_issues_count":3,"forks_count":6,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-27T22:01:36.598Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/systemed.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-10-08T17:52:48.000Z","updated_at":"2024-10-16T08:21:18.000Z","dependencies_parsed_at":"2023-02-04T09:01:48.305Z","dependency_job_id":null,"html_url":"https://github.com/systemed/glug","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemed%2Fglug","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemed%2Fglug/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemed%2Fglug/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemed%2Fglug/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/systemed","download_url":"https://codeload.github.com/systemed/glug/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248844146,"owners_count":21170518,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-15T08:16:25.719Z","updated_at":"2025-04-14T08:22:16.840Z","avatar_url":"https://github.com/systemed.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Glug\n\nText-based markup for MapLibre and Mapbox GL.\n\nGlug is a compact markup 'language' that compiles to GL JSON styles. It's implemented as a Ruby Domain-Specific Language (DSL), with all the flexibility that affords.\n\nUnlike CartoCSS and MapCSS, Glug does not cascade rules as standard. Cascading can produce a large number of rules, which is bad for mobile performance, and can make styles difficult to manage. Instead, Glug encourages concise styling by nesting layer definitions, with limited cascading as an option.\n\nGlug is a compiler. You should use it to generate JSON, then serve that JSON with your maps. Don't use Glug on the fly in production.\n\n## A simple Glug stylesheet\n\n```ruby\nversion 8\nname \"My first stylesheet\"\nsource :osm_data, type: 'vector', url: 'http://my-server.com/osm.tilejson'\n \nlayer(:roads, zoom: 10..13, source: :osm_data) {\n    line_width 6\n    line_color 0x888888\n    on(highway=='motorway', highway=='motorway_link') { line_color :blue }\n    on(highway=='trunk', highway=='trunk_link') { line_color :green }\n    on(highway=='primary', highway=='primary_link') { line_color :red }\n    on(highway=='secondary') { line_color :orange }\n    on(highway=='residential') { line_width 4 }\n}\n```\n\n## Installation and running\n\n`gem install glug`\n\nRun glug from the command line:\n\n`glug my_stylesheet.glug \u003e my_stylesheet.json`\n\nUse Glug from Ruby:\n\n```ruby\nrequire 'glug'\n\njson = Glug::Stylesheet.new {\n  version 8\n  center [0.5,53]\n}.to_json\n```\n\nYou should refer to the [Mapbox GL style documentation](https://www.mapbox.com/mapbox-gl-style-spec/) to understand GL styles and their properties. This README only explains how Glug expresses those properties.\n\n## Stylesheet and sources\n\nStylesheet-wide properties are defined simply:\n\n```ruby\n  version 8\n  center [-1.3,51.5]\n```\n\nSources are defined as hashes, with the source name specified as a symbol:\n\n```ruby\n  source :mapbox_streets, type: 'vector',\n         url: 'mapbox://mapbox.mapbox-streets-v5', default: true\n```\n\nNote the `default: true` extension. This registers the source as the default for your style, so you don't have to specify it in every layer.\n\n## Layers - the basics\n\nLayers are the meat of the styling, where Glug does most of its work.\n\n### Creating a layer\n\nYou create a layer like this:\n\n```ruby\n  layer(:water, zoom: 5..13, source: :osm_data) {\n    # Style definitions go here...\n  }\n```\n\nThe layer call begins with the layer id (`:water`) and then any additional layer-wide properties (source, source_layer, metadata, interactive). If no source is specified, the default will be used. If no source_layer is specified, the layer id will be used - so in this case, Glug would assume a source_layer of 'water'.\n\nZoom levels are always specified as Ruby ranges (`5..13`) rather than separate minzoom/maxzoom properties.\n\n### Style definitions\n\nStyle properties are defined as you'd expect:\n\n```ruby\n  line_width 5\n  line_color 0xFF07C3\n```\n\nGlug will automatically create the 'type' property for you based on the styles you define. Use 'line_width' or 'line_color', and Glug will set 'type' to 'line'. GL styles don't allow you to mix different types (e.g. lines and fills) within one layer.\n\nUse underscores in property names where the GL style spec has a hyphen, so 'line_color' rather than 'line-color'.\n\nWhen defining colours, note that Ruby specifies hex colours like so: `0xC38291`. This means you need to specify all six digits of a hex colour, so `0xCC3388` rather than '#C38'. You can avoid this by supplying a string instead: `\"#C38\"`.\n\nYou can use either symbols (`:blue`) or strings (`\"blue\"`): both will be written out as strings.\n\n### Filters and expressions\n\nGlug wraps GL styles' powerful [expressions](https://docs.mapbox.com/mapbox-gl-js/style-spec/expressions/) in a more familiar format, so you can easily make your styles react to tags/attributes. At its simplest, Glug allows you to add a test like this:\n\n```ruby\n  filter highway=='primary'\n```\n\nAdding this to a layer will mean the style only applies to primary roads. It uses Ruby's `==` test, not the single '=' you might expect from CSS-based language. You can use other operators, including numeric ones:\n\n```ruby\n  filter population\u003c30000\n```\nand 'in'/'not_in' lists:\n\n```ruby\n  filter amenity.in('pub','cafe','restaurant')\n```\n\nYou can separate tests with commas to match multiple choices:\n\n```\n  filter amenity=='pub', tourism=='hotel'\n```\n\nYou can join tests together with `\u0026` (and) and `|` (or) operators:\n\n```ruby\n  filter (place=='town') \u0026 (population\u003e100000)\n```\n\nYou can combine several such operators, but be liberal with parentheses to make the precedence clear:\n\n```ruby\n  filter ( (place=='town') \u0026 (population\u003e100000) ) | (place=='city')\n```\nAlternatively, you can also express multiple choices with the `any[]` and `all[]` operators:\n\n```ruby\n  filter any[amenity=='pub', tourism=='hotel', amenity=='restaurant']\n```\n\n### Using expressions as properties\n\nYou can also use expressions to set GL properties programmatically. For example, to set the colour of a circle based on the 'temperature' tag in the vector tile:\n\n```ruby\n  circle_color rgb(temperature, 0, temperature/2)\n```\n\nIf you're following along with the GL [expressions spec](https://docs.mapbox.com/mapbox-gl-js/style-spec/expressions/), GL JSON operators are expressed as an array, and in Glug we write them as a operator name ('rgb') followed by the arguments in parentheses.\n\nA more complex example:\n\n```ruby\n  fill_color let('density', population/sqkm) \u003c\u003c\n    interpolate([:linear], zoom(),\n      8,  interpolate([:linear], var('density'), 274, to_color(\"#edf8e9\"), 1551, to_color(\"#006d2c\")),\n      10, interpolate([:linear], var('density'), 274, to_color(\"#eff3ff\"), 1551, to_color(\"#08519c\"))\n    )\n```\n\nSeveral operators can also be used as dot (postfix) methods. For example, `name.length` will return the length of the name tag; `name.upcase` will return it in upper case; and `in` can be used with a list of values, e.g. `ref.in(\"M1\",\"M5\",\"M6\")`. For the list of operators where this applies, see DOT_METHODS in condition.rb.\n\nNote the following:\n\n* You don't need to use the `get` operator - you can just write the tag name, e.g. `temperature`. (You can still use `get` in case of a clash with a reserved word.)\n* For the GL operator 'format', write `string_format` instead; for 'id', write `feature_id`; for 'case', write `case_when`; for the '!' operator, write `_!`. (These are Ruby reserved words or used elsewhere in Glug.)\n* Where a GL operator has a dash, write it with an underscore (e.g. `to_color`).\n* Arrays can be accessed in standard bracketed subscript notation, e.g. `colours[5]` (compiled to 'at' in the GL style).\n* Within colour operators, you'll need to write colours as strings (e.g. \"#FF0000\") rather than Ruby hex values.\n* To concatenate two operators (often where the first is `let`), use `\u003c\u003c`.\n* You may still need to use `literal(1,2,3)` to write an array or object value.\n\n\n## Sublayers and cascading\n\n### Sublayers\n\nFilter expressions come into their own when defining sublayers.\n\nA sublayer inherits all the properties of its parent layer, and adds more, if a test is fulfilled. For example, if you wanted to show all roads 2px wide, but motorways 4px wide:\n\n```ruby\n  layer(:roads) {\n    line_color :black\n    line_width 2\n    on(highway=='motorway') { line_width 4 }\n  }\n```\n\nSublayers are introduced with the `on` instruction, which expects either a zoom range, an expression, or both. The following are all valid:\n\n```ruby\n  on(highway=='motorway') { line_width 4 }\n  on(8..12, highway=='motorway') { line_color :blue }\n  on(3..6) { line_width 2 }\n  on(8, highway=='motorway', oneway='yes') { line-width 2 }\n```\n\nSublayers can be nested:\n\n```ruby\n  on(3..6) {\n    line_width 2\n    on(highway=='motorway') { line-color :blue }\n  }\n```\nDo not add a space between `on` and the parentheses. If your filter breaks, add more parentheses.\n\nSometimes, you may wish to only generate the sublayers, and suppress the partially unstyled parent layer. You can achieve this with the `suppress` instruction:\n\n```ruby\n  layer(:roads) {\n    line_width 4\n    on(highway=='trunk') { line_color :green }\n    on(highway=='primary') { line_color :blue }\n    on(highway=='secondary') { line_color :orange }\n    suppress\n  }\n```\n\nSublayers have no special meaning in GL styles; they are normal layers like any other. Glug unwraps the 'inherited' properties and creates a layer accordingly. Points to note:\n\n* Glug names sublayers automatically: the first sublayer of `roads` will be `roads__1`. If you want to give a sublayer an explicit layer id, write `id :minor_roads`.\n* If two layers share a source, filter, zoom levels, type, and certain ('layout') properties, the GL renderer can optimise drawing by reusing the same definition. Glug does this invisibly so you don't need to specify it in your style.\n* Layer ordering follows the order of your stylesheet.\n* Nested zoom levels simply overwrite their 'parents', so a zoom 7 nested within a zoom 3..6 will still render at zoom 7.\n\n### Cascading\n\nAn intentionally limited form of cascading is provided:\n\n```ruby\n  layer(:roads) {\n    line_width 4\n\n    cascade(motor_vehicle=='no') { line_width 2 }\n    uncascaded(motor_vehicle!='no')\n\n    on(highway=='trunk') { line_color :green }\n    on(highway=='primary') { line_color :blue }\n    suppress\n  }\n```\n\nFor each sublayer, a cascaded variant is applied with the `motor_vehicle=='no'` test. The uncascaded sublayer gets an extra condition, too, to avoid both versions being drawn when `motor_vehicle=='no'`.\n\nThe result is four layers:\n\n1. If `highway=='trunk'` \u0026 `motor_vehicle=='no'`, draw `line_color: :green` and `line_width 2`\n2. If `highway=='primary'` \u0026 `motor_vehicle=='no'`, draw `line_color: :blue` and `line_width 2`\n3. If `highway=='trunk'` \u0026 `motor_vehicle!='no'`, draw `line_color: :green` and `line_width 4`\n4. If `highway=='primary'` \u0026 `motor_vehicle!='no'`, draw `line_color: :blue` and `line_width 4`\n\nThe `cascade` instruction applies to sublayers created below it, but not to those created above, or to the parent layer. Cascades do not multiply each other, so if you write\n\n```ruby\n    cascade(motor_vehicle=='no') { line_width 2 }\n    cascade(route=='bus') { line_color :red }\n```\n\nthis will not create rules for a combined `motor_vehicle=='no' \u0026 route=='bus'` condition - you must do that yourself.\n\nEach cascade doubles the number of sublayer rules, so use them with great care!\n\n### Still Ruby\n\nDespite these additions, Glug is Ruby at heart so you can use comments, variables, includes, multi-statement lines, do/end blocks, all as you'd expect. Don't expect error-trapping to quite be the same - since Glug interprets unknown words as tag keys, errors can sometimes be swallowed up.\n\nYou can even use Ruby's lambdas to set a value as a fraction of the previously set one:\n\n```ruby\n  line_width 4\n  on(urban==true) { line_width -\u003e(old_value){ old_value/2.0 } }\n```\n\n## To do\n\n* Glug is in alpha. Things may break.\n* Glug doesn't yet support class-specific paint properties.\n* Glug doesn't yet do anything clever with sprite or glyph directives, but maybe it should.\n\n## Contributing\n\nBug reports, suggestions and (especially!) pull requests are very welcome on the Github issue tracker. Please check the tracker to see if your issue is already known, and be nice. For questions, please use IRC (irc.oftc.net or http://irc.osm.org, channel #osm-dev) and http://help.osm.org.\n\nFormatting: braces and indents as shown, hard tabs (4sp). (Yes, I know.) Please be conservative about adding dependencies.\n\n## Copyright and contact\n\nRichard Fairhurst, 2022. This code is licensed as FTWPL; you may do anything you like with this code and there is no warranty.\n\nIf you'd like to sponsor development of Glug, you can contact me at richard@systemeD.net.\n\nCheck out [tilemaker](https://github.com/systemed/tilemaker) to produce the vector tiles which MapLibre and Mapbox GL consume.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsystemed%2Fglug","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsystemed%2Fglug","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsystemed%2Fglug/lists"}