From a0a7cfda2a12a0fefb14b9bc6ab7c93338efa4b3 Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Tue, 13 Jan 2026 16:27:43 +0000 Subject: [PATCH 1/3] Support using ivars in stylesheets This allows you to define and use instance variables in both the stylesheet context and also within layers. They operate as if the glug files were all part of one large ruby class. This is similar behaviour to "@" variables in LESS, so will feel familiar to anyone coming from a CartoCSS background. --- .rubocop_todo.yml | 2 +- lib/glug/layer.rb | 16 +++++++++++++++ lib/glug/stylesheet.rb | 2 +- spec/fixtures/ivars.glug | 23 +++++++++++++++++++++ spec/fixtures/ivars.json | 35 ++++++++++++++++++++++++++++++++ spec/lib/glug/stylesheet_spec.rb | 9 ++++++++ 6 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/ivars.glug create mode 100644 spec/fixtures/ivars.json diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 920eeb6..8f18c2e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -14,7 +14,7 @@ Metrics/AbcSize: # Offense count: 2 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 208 + Max: 217 # Offense count: 3 # Configuration parameters: AllowedMethods, AllowedPatterns. diff --git a/lib/glug/layer.rb b/lib/glug/layer.rb index 39ff310..d98bedf 100644 --- a/lib/glug/layer.rb +++ b/lib/glug/layer.rb @@ -82,7 +82,23 @@ def initialize(stylesheet, args = {}) end def dsl_eval(&block) + # Copy ivars from stylesheet DSL to layer DSL (before eval) + style_dsl = @stylesheet.dsl + style_dsl.instance_variables.each do |ivar| + next if ivar.to_s.start_with?('@__') # skip internal ivars + + @dsl.instance_variable_set(ivar, style_dsl.instance_variable_get(ivar)) + end + + # Run the layer evaluation @dsl.instance_eval(&block) + + # Copy ivars back to stylesheet DSL (after eval) + @dsl.instance_variables.each do |ivar| + next if ivar.to_s.start_with?('@__') # skip internal ivars + + style_dsl.instance_variable_set(ivar, @dsl.instance_variable_get(ivar)) + end end # Handle all missing 'method' calls diff --git a/lib/glug/stylesheet.rb b/lib/glug/stylesheet.rb index 8223f19..0b5a334 100644 --- a/lib/glug/stylesheet.rb +++ b/lib/glug/stylesheet.rb @@ -3,7 +3,7 @@ module Glug # the main document object class Stylesheet - attr_accessor :sources, :kv, :base_dir, :params + attr_accessor :sources, :kv, :base_dir, :params, :dsl def initialize(base_dir: nil, params: nil, &block) @sources = {} diff --git a/spec/fixtures/ivars.glug b/spec/fixtures/ivars.glug new file mode 100644 index 0000000..4f90f91 --- /dev/null +++ b/spec/fixtures/ivars.glug @@ -0,0 +1,23 @@ +version 8 +name 'My first stylesheet' +source :shortbread, type: 'vector', url: 'https://vector.openstreetmap.org/shortbread_v1/tilejson.json' + +# set at stylesheet level +@width = 6 + +layer(:roads, zoom: 10..13, source: :shortbread) do + # get at layer level + line_width @width + line_color 0x888888 +end + +layer(:water, source: :shortbread) do + # set at layer level + @water = 'blue' + fill_color @water +end + +layer(:water_line, source: :shortbread) do + # get from other layer + fill_color @water +end diff --git a/spec/fixtures/ivars.json b/spec/fixtures/ivars.json new file mode 100644 index 0000000..14669b8 --- /dev/null +++ b/spec/fixtures/ivars.json @@ -0,0 +1,35 @@ +{ + "version":8, + "name":"My first stylesheet", + "sources":{ + "shortbread":{ + "type":"vector", + "url":"https://vector.openstreetmap.org/shortbread_v1/tilejson.json" + } + }, + "layers":[ + { + "paint":{"line-width":6,"line-color":"#888888"}, + "source":"shortbread", + "id":"roads", + "source-layer":"roads", + "type":"line", + "minzoom":10, + "maxzoom":13 + }, + { + "paint":{"fill-color":"blue"}, + "source":"shortbread", + "id":"water", + "source-layer":"water", + "type":"fill" + }, + { + "paint":{"fill-color":"blue"}, + "source":"shortbread", + "id":"water_line", + "source-layer":"water_line", + "type":"fill" + } + ] +} diff --git a/spec/lib/glug/stylesheet_spec.rb b/spec/lib/glug/stylesheet_spec.rb index c3654fc..b685569 100644 --- a/spec/lib/glug/stylesheet_spec.rb +++ b/spec/lib/glug/stylesheet_spec.rb @@ -86,5 +86,14 @@ output = File.read(File.join(fixture_dir, 'basic_with_include.json')) expect(stylesheet.to_json).to eql(output.strip) end + + it 'sets and gets instance variables across the whole stylesheet' do + glug = File.read(File.join(fixture_dir, 'ivars.glug')) + stylesheet = described_class.new(base_dir: fixture_dir) do + instance_eval(glug) + end + output = File.read(File.join(fixture_dir, 'ivars.json')) + expect(stylesheet.to_json).to eql(output.strip) + end end end From 3b736979db5c7233de93b2685509bc9e82d6f834 Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Tue, 13 Jan 2026 16:50:17 +0000 Subject: [PATCH 2/3] Add spec to show ivars work across multiple files --- spec/fixtures/ivars_with_include.glug | 8 ++++++++ spec/fixtures/ivars_with_include.json | 21 +++++++++++++++++++++ spec/fixtures/ivars_with_include_sub.glug | 4 ++++ spec/lib/glug/stylesheet_spec.rb | 9 +++++++++ 4 files changed, 42 insertions(+) create mode 100644 spec/fixtures/ivars_with_include.glug create mode 100644 spec/fixtures/ivars_with_include.json create mode 100644 spec/fixtures/ivars_with_include_sub.glug diff --git a/spec/fixtures/ivars_with_include.glug b/spec/fixtures/ivars_with_include.glug new file mode 100644 index 0000000..4b612a8 --- /dev/null +++ b/spec/fixtures/ivars_with_include.glug @@ -0,0 +1,8 @@ +version 8 +name 'My first stylesheet' +source :shortbread, type: 'vector', url: 'https://vector.openstreetmap.org/shortbread_v1/tilejson.json' + +# set at stylesheet level +@width = 6 + +include_file 'ivars_with_include_sub.glug' diff --git a/spec/fixtures/ivars_with_include.json b/spec/fixtures/ivars_with_include.json new file mode 100644 index 0000000..62083cf --- /dev/null +++ b/spec/fixtures/ivars_with_include.json @@ -0,0 +1,21 @@ +{ + "version":8, + "name":"My first stylesheet", + "sources":{ + "shortbread":{ + "type":"vector", + "url":"https://vector.openstreetmap.org/shortbread_v1/tilejson.json" + } + }, + "layers":[ + { + "paint":{"line-width":6,"line-color":"#888888"}, + "source":"shortbread", + "id":"roads", + "source-layer":"roads", + "type":"line", + "minzoom":10, + "maxzoom":13 + } + ] +} diff --git a/spec/fixtures/ivars_with_include_sub.glug b/spec/fixtures/ivars_with_include_sub.glug new file mode 100644 index 0000000..21c6a99 --- /dev/null +++ b/spec/fixtures/ivars_with_include_sub.glug @@ -0,0 +1,4 @@ +layer(:roads, zoom: 10..13, source: :shortbread) do + line_width @width + line_color 0x888888 +end diff --git a/spec/lib/glug/stylesheet_spec.rb b/spec/lib/glug/stylesheet_spec.rb index b685569..e6a5c7f 100644 --- a/spec/lib/glug/stylesheet_spec.rb +++ b/spec/lib/glug/stylesheet_spec.rb @@ -95,5 +95,14 @@ output = File.read(File.join(fixture_dir, 'ivars.json')) expect(stylesheet.to_json).to eql(output.strip) end + + it 'sets and gets instance variables across included stylesheets' do + glug = File.read(File.join(fixture_dir, 'ivars_with_include.glug')) + stylesheet = described_class.new(base_dir: fixture_dir) do + instance_eval(glug) + end + output = File.read(File.join(fixture_dir, 'ivars_with_include.json')) + expect(stylesheet.to_json).to eql(output.strip) + end end end From 022714ec89771139a6a27a5293387c2dd2d774ed Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Mon, 23 Mar 2026 16:30:59 +0000 Subject: [PATCH 3/3] Ensure expressions assigned to ivars are stored as conditions --- lib/glug/stylesheet_dsl.rb | 6 +++++- spec/fixtures/expression_ivars.glug | 12 ++++++++++++ spec/fixtures/expression_ivars.json | 23 +++++++++++++++++++++++ spec/lib/glug/stylesheet_spec.rb | 9 +++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/expression_ivars.glug create mode 100644 spec/fixtures/expression_ivars.json diff --git a/lib/glug/stylesheet_dsl.rb b/lib/glug/stylesheet_dsl.rb index 12361f5..9590090 100644 --- a/lib/glug/stylesheet_dsl.rb +++ b/lib/glug/stylesheet_dsl.rb @@ -26,7 +26,11 @@ def respond_to_missing?(*) # Set a property, e.g. 'bearing 29' def method_missing(method_sym, *args) - @__impl.add_property(method_sym, *args) + if Layer::EXPRESSIONS.include?(method_sym) + Condition.new.from_list(method_sym, args) + else + @__impl.add_property(method_sym, *args) + end end end end diff --git a/spec/fixtures/expression_ivars.glug b/spec/fixtures/expression_ivars.glug new file mode 100644 index 0000000..ea17541 --- /dev/null +++ b/spec/fixtures/expression_ivars.glug @@ -0,0 +1,12 @@ +version 8 +name 'Expression ivars test' +source :shortbread, type: 'vector', url: 'https://vector.openstreetmap.org/shortbread_v1/tilejson.json' + +# Expression assigned to ivar at stylesheet level +@my_halo = rgba(255, 255, 255, 0.8) +@my_color = rgb(100, 200, 50) + +layer(:labels, zoom: 5.., source: :shortbread) do + text_halo_color @my_halo + text_color @my_color +end diff --git a/spec/fixtures/expression_ivars.json b/spec/fixtures/expression_ivars.json new file mode 100644 index 0000000..ccf97c9 --- /dev/null +++ b/spec/fixtures/expression_ivars.json @@ -0,0 +1,23 @@ +{ + "version":8, + "name":"Expression ivars test", + "sources":{ + "shortbread":{ + "type":"vector", + "url":"https://vector.openstreetmap.org/shortbread_v1/tilejson.json" + } + }, + "layers":[ + { + "paint":{ + "text-halo-color":["rgba",255,255,255,0.8], + "text-color":["rgb",100,200,50] + }, + "source":"shortbread", + "id":"labels", + "source-layer":"labels", + "type":"symbol", + "minzoom":5 + } + ] +} \ No newline at end of file diff --git a/spec/lib/glug/stylesheet_spec.rb b/spec/lib/glug/stylesheet_spec.rb index e6a5c7f..f25811e 100644 --- a/spec/lib/glug/stylesheet_spec.rb +++ b/spec/lib/glug/stylesheet_spec.rb @@ -104,5 +104,14 @@ output = File.read(File.join(fixture_dir, 'ivars_with_include.json')) expect(stylesheet.to_json).to eql(output.strip) end + + it 'stores expressions in ivars at stylesheet level' do + glug = File.read(File.join(fixture_dir, 'expression_ivars.glug')) + stylesheet = described_class.new(base_dir: fixture_dir) do + instance_eval(glug) + end + output = File.read(File.join(fixture_dir, 'expression_ivars.json')) + expect(stylesheet.to_json).to eql(output.strip) + end end end