dugway 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +1 -1
- data/README.md +32 -7
- data/dugway.gemspec +11 -9
- data/lib/dugway/cli/server.rb +67 -7
- data/lib/dugway/liquid/drops/artists_drop.rb +12 -0
- data/lib/dugway/liquid/drops/base_drop.rb +27 -2
- data/lib/dugway/liquid/drops/categories_drop.rb +12 -0
- data/lib/dugway/liquid/drops/pages_drop.rb +30 -2
- data/lib/dugway/liquid/drops/product_drop.rb +11 -0
- data/lib/dugway/liquid/drops/products_drop.rb +39 -0
- data/lib/dugway/liquid/drops/theme_drop.rb +29 -6
- data/lib/dugway/liquid/filters/core_filters.rb +169 -7
- data/lib/dugway/liquid/filters/font_filters.rb +1 -0
- data/lib/dugway/liquid/filters/util_filters.rb +1 -10
- data/lib/dugway/liquid/tags/get.rb +6 -6
- data/lib/dugway/liquid/tags/paginate.rb +61 -11
- data/lib/dugway/store.rb +39 -1
- data/lib/dugway/theme.rb +72 -33
- data/lib/dugway/version.rb +1 -1
- data/lib/dugway.rb +24 -1
- data/locales/storefront.de.yml +2 -0
- data/locales/storefront.en-CA.yml +2 -0
- data/locales/storefront.en-GB.yml +2 -0
- data/locales/storefront.en-US.yml +2 -0
- data/locales/storefront.es-ES.yml +2 -0
- data/locales/storefront.es-MX.yml +2 -0
- data/locales/storefront.fr-CA.yml +2 -0
- data/locales/storefront.fr-FR.yml +2 -0
- data/locales/storefront.id.yml +2 -0
- data/locales/storefront.it.yml +2 -0
- data/locales/storefront.ja.yml +2 -0
- data/locales/storefront.ko.yml +2 -0
- data/locales/storefront.nl.yml +2 -0
- data/locales/storefront.pl.yml +2 -0
- data/locales/storefront.pt-BR.yml +2 -0
- data/locales/storefront.pt-PT.yml +2 -0
- data/locales/storefront.ro.yml +2 -0
- data/locales/storefront.sv.yml +2 -0
- data/locales/storefront.tr.yml +2 -0
- data/locales/storefront.zh-CN.yml +2 -0
- data/locales/storefront.zh-TW.yml +2 -0
- data/mise.toml +2 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/units/dugway/liquid/drops/pages_drop_spec.rb +186 -7
- data/spec/units/dugway/liquid/drops/product_drop_spec.rb +17 -0
- data/spec/units/dugway/liquid/filters/core_filters_spec.rb +301 -3
- data/spec/units/dugway/store_spec.rb +18 -0
- data/spec/units/dugway/theme_spec.rb +246 -0
- metadata +54 -25
@@ -1,15 +1,6 @@
|
|
1
1
|
module Dugway
|
2
2
|
module Filters
|
3
3
|
module UtilFilters
|
4
|
-
private
|
5
|
-
|
6
|
-
# Registered Vars
|
7
|
-
|
8
|
-
def currency
|
9
|
-
@context.registers[:currency]
|
10
|
-
end
|
11
|
-
|
12
|
-
# Mimics
|
13
4
|
|
14
5
|
DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",", :precision => 2, :significant => false, :strip_insignificant_zeros => false }
|
15
6
|
|
@@ -126,7 +117,7 @@ module Dugway
|
|
126
117
|
|
127
118
|
def content_tag(type, content, options = {})
|
128
119
|
result = tag(type, options, true)
|
129
|
-
result += content
|
120
|
+
result += content.to_s
|
130
121
|
result += "</#{type}>"
|
131
122
|
end
|
132
123
|
|
@@ -5,16 +5,16 @@ module Dugway
|
|
5
5
|
|
6
6
|
def initialize(tag_name, markup, tokens)
|
7
7
|
if markup =~ Syntax
|
8
|
-
@number_to_get =
|
9
|
-
@variable_name =
|
10
|
-
@collection_name =
|
8
|
+
@number_to_get = Regexp.last_match(1).present? ? Regexp.last_match(2) : nil
|
9
|
+
@variable_name = Regexp.last_match(3)
|
10
|
+
@collection_name = Regexp.last_match(4)
|
11
11
|
|
12
12
|
@attributes = {}
|
13
13
|
markup.scan(Liquid::TagAttributes) { |key, value|
|
14
14
|
@attributes[key] = value
|
15
15
|
}
|
16
16
|
else
|
17
|
-
raise SyntaxError
|
17
|
+
raise SyntaxError, "Syntax Error in tag 'get' - Valid syntax: get [number] [items] from [collection] order:[order]"
|
18
18
|
end
|
19
19
|
|
20
20
|
super
|
@@ -48,9 +48,9 @@ module Dugway
|
|
48
48
|
|
49
49
|
context[@variable_name] = context[@collection_name]
|
50
50
|
|
51
|
-
raise ArgumentError
|
51
|
+
raise ArgumentError, "Cannot get array '#{@collection_name}'. Not found." if context[@variable_name].total_entries.nil?
|
52
52
|
|
53
|
-
|
53
|
+
super(context)
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
@@ -5,9 +5,9 @@ module Dugway
|
|
5
5
|
|
6
6
|
def initialize(tag_name, markup, tokens)
|
7
7
|
if markup =~ Syntax
|
8
|
-
@variable_name =
|
9
|
-
@collection_name =
|
10
|
-
@per_page =
|
8
|
+
@variable_name = Regexp.last_match(1)
|
9
|
+
@collection_name = Regexp.last_match(2)
|
10
|
+
@per_page = Regexp.last_match(3).present? ? Regexp.last_match(4) : nil
|
11
11
|
|
12
12
|
@attributes = { 'inner_window' => 3, 'outer_window' => 1 }
|
13
13
|
|
@@ -17,7 +17,7 @@ module Dugway
|
|
17
17
|
|
18
18
|
@limit = @attributes['limit']
|
19
19
|
else
|
20
|
-
raise SyntaxError
|
20
|
+
raise SyntaxError, "Syntax Error in tag 'paginate' - Valid syntax: paginate [variable] from [collection] by [number]"
|
21
21
|
end
|
22
22
|
|
23
23
|
super
|
@@ -29,6 +29,7 @@ module Dugway
|
|
29
29
|
@per_page = context[@per_page].present? ? context[@per_page] : (@per_page.present? ? @per_page.to_i : nil)
|
30
30
|
@order = context[@attributes['order']].present? ? context[@attributes['order']] : @attributes['order']
|
31
31
|
|
32
|
+
|
32
33
|
context.stack do
|
33
34
|
context['internal'] = {
|
34
35
|
'per_page' => @per_page,
|
@@ -37,21 +38,45 @@ module Dugway
|
|
37
38
|
'limit' => @limit
|
38
39
|
}
|
39
40
|
|
40
|
-
|
41
|
+
# Handle nested property access like "products.current"
|
42
|
+
collection = resolve_collection_path(context, @collection_name)
|
43
|
+
|
41
44
|
context[@variable_name] = collection
|
42
|
-
|
45
|
+
|
46
|
+
begin
|
47
|
+
current_page = collection.current_page
|
48
|
+
rescue StandardError => e
|
49
|
+
puts "Warning: Failed to get current_page: #{e.message}" if ENV['DEBUG']
|
50
|
+
current_page = 1
|
51
|
+
end
|
52
|
+
|
53
|
+
begin
|
54
|
+
page_size = collection.per_page
|
55
|
+
offset = collection.offset
|
56
|
+
rescue StandardError => e
|
57
|
+
puts "Warning: Failed to get pagination info: #{e.message}" if ENV['DEBUG']
|
58
|
+
page_size = 10
|
59
|
+
offset = 0
|
60
|
+
end
|
43
61
|
|
44
62
|
pagination = {
|
45
|
-
'page_size' =>
|
63
|
+
'page_size' => page_size,
|
46
64
|
'current_page' => current_page,
|
47
|
-
'current_offset' =>
|
65
|
+
'current_offset' => offset
|
48
66
|
}
|
49
67
|
|
50
68
|
context['paginate'] = pagination
|
51
69
|
|
52
|
-
|
70
|
+
begin
|
71
|
+
collection_size = collection.total_entries
|
72
|
+
rescue StandardError => e
|
73
|
+
puts "Warning: Failed to get total_entries: #{e.message}" if ENV['DEBUG']
|
74
|
+
collection_size = collection.respond_to?(:size) ? collection.size : 0
|
75
|
+
end
|
53
76
|
|
54
|
-
|
77
|
+
if collection_size.nil? || collection_size == 0
|
78
|
+
return ""
|
79
|
+
end
|
55
80
|
|
56
81
|
page_count = collection.total_pages
|
57
82
|
|
@@ -103,12 +128,37 @@ module Dugway
|
|
103
128
|
}
|
104
129
|
end
|
105
130
|
|
106
|
-
|
131
|
+
super(context)
|
107
132
|
end
|
108
133
|
end
|
109
134
|
|
110
135
|
private
|
111
136
|
|
137
|
+
def resolve_collection_path(context, path)
|
138
|
+
return context[path] unless path.include?('.')
|
139
|
+
|
140
|
+
parts = path.split('.')
|
141
|
+
collection = context[parts.shift]
|
142
|
+
|
143
|
+
parts.each do |part|
|
144
|
+
break unless collection
|
145
|
+
|
146
|
+
if collection.respond_to?(part)
|
147
|
+
collection = collection.public_send(part)
|
148
|
+
elsif collection.respond_to?(:liquid_method_missing)
|
149
|
+
collection = collection.liquid_method_missing(part)
|
150
|
+
else
|
151
|
+
collection = nil
|
152
|
+
break
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
collection || []
|
157
|
+
rescue StandardError => e
|
158
|
+
puts "Warning: Failed to resolve collection '#{path}': #{e.message}" if ENV['DEBUG']
|
159
|
+
[]
|
160
|
+
end
|
161
|
+
|
112
162
|
def no_link(title)
|
113
163
|
{ 'title' => title.to_s, 'is_link' => false }
|
114
164
|
end
|
data/lib/dugway/store.rb
CHANGED
@@ -36,7 +36,7 @@ module Dugway
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def pages
|
39
|
-
@pages ||= theme_pages + custom_pages
|
39
|
+
@pages ||= theme_pages + custom_pages + external_pages + required_pages
|
40
40
|
end
|
41
41
|
|
42
42
|
def page(permalink)
|
@@ -136,6 +136,44 @@ module Dugway
|
|
136
136
|
Dugway.options.dig(:store, :instant_checkout) || false
|
137
137
|
end
|
138
138
|
|
139
|
+
def subscribe_url
|
140
|
+
@store_options[:subscribe_url] || account['subscribe_url']
|
141
|
+
end
|
142
|
+
|
143
|
+
def price_suffix
|
144
|
+
@store_options[:price_suffix]
|
145
|
+
end
|
146
|
+
|
147
|
+
def external_pages
|
148
|
+
@external_pages ||= begin
|
149
|
+
pages = []
|
150
|
+
if subscribe_url && !subscribe_url.empty?
|
151
|
+
pages << {
|
152
|
+
'name' => 'Subscribe',
|
153
|
+
'permalink' => 'subscribe',
|
154
|
+
'url' => subscribe_url,
|
155
|
+
'category' => 'external'
|
156
|
+
}
|
157
|
+
end
|
158
|
+
pages
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def required_pages
|
163
|
+
@required_pages ||= begin
|
164
|
+
required_pages_config = @store_options[:required_pages] || account['required_pages'] || []
|
165
|
+
required_pages_config.map do |page_config|
|
166
|
+
{
|
167
|
+
'name' => page_config['name'],
|
168
|
+
'permalink' => page_config['permalink'],
|
169
|
+
'url' => page_config['url'],
|
170
|
+
'category' => page_config['category'] || 'custom',
|
171
|
+
'required' => true
|
172
|
+
}
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
139
177
|
private
|
140
178
|
|
141
179
|
def get(path)
|
data/lib/dugway/theme.rb
CHANGED
@@ -294,7 +294,10 @@ module Dugway
|
|
294
294
|
def validate_options_requires
|
295
295
|
return unless settings['options']
|
296
296
|
all_variables = settings['options'].map { |o| o['variable'] }
|
297
|
-
|
297
|
+
|
298
|
+
# Include variables from images and image_sets settings
|
299
|
+
all_variables.concat(settings['images'].map { |i| i['variable'] }) if settings['images']
|
300
|
+
all_variables.concat(settings['image_sets'].map { |i| i['variable'] }) if settings['image_sets']
|
298
301
|
|
299
302
|
settings['options'].each do |option|
|
300
303
|
next unless option['requires']
|
@@ -316,45 +319,81 @@ module Dugway
|
|
316
319
|
|
317
320
|
# Process each rule in the array
|
318
321
|
option['requires'].each do |rule|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
322
|
+
validate_requires_rule(rule, option['variable'], all_variables)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# Validates individual requires rule - handles both string rules and object rules (like 'any')
|
328
|
+
def validate_requires_rule(rule, option_variable, all_variables)
|
329
|
+
# Handle object-based rules (like {"any": [...]})
|
330
|
+
if rule.is_a?(Hash)
|
331
|
+
if rule.has_key?('any')
|
332
|
+
validate_any_condition(rule['any'], option_variable, all_variables)
|
333
|
+
else
|
334
|
+
@errors << "Option '#{option_variable}' has unsupported requires object. Only 'any' is currently supported."
|
335
|
+
end
|
336
|
+
return
|
337
|
+
end
|
333
338
|
|
334
|
-
|
335
|
-
|
336
|
-
is_valid_operator = ['eq', 'neq'].include?(operator)
|
339
|
+
# Handle string-based rules (existing logic)
|
340
|
+
return if rule == 'inventory'
|
337
341
|
|
338
|
-
|
342
|
+
valid_operators = %w(eq neq gt lt gte lte present)
|
339
343
|
|
340
|
-
|
344
|
+
# Extract setting name and optional operator/value
|
345
|
+
parts = rule.split(/\s+/)
|
346
|
+
setting_name = parts[0]
|
347
|
+
operator = parts[1]
|
348
|
+
value = parts[2]
|
349
|
+
|
350
|
+
if setting_name.start_with?('feature:')
|
351
|
+
# Validate feature flag format
|
352
|
+
unless setting_name =~ /^feature:[a-z_]+$/
|
353
|
+
@errors << "Option '#{option_variable}' has invalid feature flag format"
|
354
|
+
return
|
355
|
+
end
|
341
356
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
end
|
357
|
+
# Only validate operator/value if they exist
|
358
|
+
if operator
|
359
|
+
is_valid_operator = ['eq', 'neq'].include?(operator)
|
346
360
|
|
347
|
-
#
|
348
|
-
unless all_variables.include?(setting_name)
|
349
|
-
@errors << "Option '#{option['variable']}' requires unknown setting '#{setting_name}'"
|
350
|
-
next # Skip further checks for this rule if setting is unknown
|
351
|
-
end
|
361
|
+
@errors << "Option '#{option_variable}' has invalid operator '#{operator}'. Feature flags can only use `eq` or `neq`." unless is_valid_operator
|
352
362
|
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
end
|
363
|
+
is_valid_value = ['visible'].include?(value)
|
364
|
+
|
365
|
+
@errors << "Option '#{option_variable}' has invalid comparison value '#{value}'. Feature flags can only check for `visible`." unless is_valid_value
|
357
366
|
end
|
367
|
+
return
|
368
|
+
end
|
369
|
+
|
370
|
+
# --- Non-feature rule validation ---
|
371
|
+
unless all_variables.include?(setting_name)
|
372
|
+
@errors << "Option '#{option_variable}' requires unknown setting '#{setting_name}'"
|
373
|
+
return # Skip further checks for this rule if setting is unknown
|
374
|
+
end
|
375
|
+
|
376
|
+
# Validate operator if present
|
377
|
+
if operator && !valid_operators.include?(operator)
|
378
|
+
@errors << "Option '#{option_variable}' has invalid operator '#{operator}'. Allowed operators are: #{valid_operators.join(', ')}."
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
# Validates 'any' condition - ensures it's an array and validates each nested rule
|
383
|
+
def validate_any_condition(any_rules, option_variable, all_variables)
|
384
|
+
unless any_rules.is_a?(Array)
|
385
|
+
@errors << "Option '#{option_variable}' has invalid 'any' condition - must be an array of rules"
|
386
|
+
return
|
387
|
+
end
|
388
|
+
|
389
|
+
if any_rules.empty?
|
390
|
+
@errors << "Option '#{option_variable}' has empty 'any' condition - must contain at least one rule"
|
391
|
+
return
|
392
|
+
end
|
393
|
+
|
394
|
+
# Validate each rule within the 'any' condition
|
395
|
+
any_rules.each do |nested_rule|
|
396
|
+
validate_requires_rule(nested_rule, option_variable, all_variables)
|
358
397
|
end
|
359
398
|
end
|
360
399
|
|
data/lib/dugway/version.rb
CHANGED
data/lib/dugway.rb
CHANGED
@@ -11,6 +11,7 @@ end
|
|
11
11
|
|
12
12
|
require 'active_support/all'
|
13
13
|
require 'i18n'
|
14
|
+
require 'i18n/backend/fallbacks'
|
14
15
|
require 'bigcartel-currency-locales'
|
15
16
|
require 'bigcartel/theme/fonts'
|
16
17
|
|
@@ -38,6 +39,14 @@ end
|
|
38
39
|
# Set available locales. Provide a default if no locale files are found (e.g., during initial setup or in case of an error).
|
39
40
|
I18n.config.available_locales = available_locales.empty? ? [:'en-US', :en] : available_locales
|
40
41
|
|
42
|
+
# Enable fallbacks for I18n
|
43
|
+
# This allows sv-SE to fallback to sv, en-GB to en, etc.
|
44
|
+
I18n::Backend::Simple.include(I18n::Backend::Fallbacks)
|
45
|
+
|
46
|
+
# Disable enforce_available_locales to allow invalid locales like 'eu'
|
47
|
+
# We'll handle them gracefully with fallbacks
|
48
|
+
I18n.enforce_available_locales = false
|
49
|
+
|
41
50
|
# Reload I18n to apply changes and load the translations from the files.
|
42
51
|
I18n.backend.reload!
|
43
52
|
|
@@ -63,7 +72,21 @@ module Dugway
|
|
63
72
|
|
64
73
|
BigCartel::CurrencyLocales.insert
|
65
74
|
I18n.default_locale = 'en-US'
|
66
|
-
|
75
|
+
|
76
|
+
# Set locale with fallback handling for invalid or missing locales
|
77
|
+
begin
|
78
|
+
requested_locale = Dugway.store.locale
|
79
|
+
if requested_locale
|
80
|
+
# Try to set the locale - if it's completely invalid (like 'eu'),
|
81
|
+
# this will fall back to default_locale due to enforce_available_locales = false
|
82
|
+
I18n.locale = requested_locale
|
83
|
+
else
|
84
|
+
I18n.locale = I18n.default_locale
|
85
|
+
end
|
86
|
+
rescue I18n::InvalidLocale
|
87
|
+
# If somehow we still get an invalid locale error, fall back to default
|
88
|
+
I18n.locale = I18n.default_locale
|
89
|
+
end
|
67
90
|
|
68
91
|
Rack::Builder.app do
|
69
92
|
use Rack::Session::Cookie, :secret => 'stopwarningmeaboutnothavingasecret'
|
data/locales/storefront.de.yml
CHANGED
data/locales/storefront.id.yml
CHANGED
data/locales/storefront.it.yml
CHANGED
data/locales/storefront.ja.yml
CHANGED
data/locales/storefront.ko.yml
CHANGED
data/locales/storefront.nl.yml
CHANGED
data/locales/storefront.pl.yml
CHANGED
data/locales/storefront.ro.yml
CHANGED
data/locales/storefront.sv.yml
CHANGED
data/locales/storefront.tr.yml
CHANGED
data/mise.toml
ADDED
data/spec/spec_helper.rb
CHANGED
@@ -10,6 +10,9 @@ RSpec.configure do |config|
|
|
10
10
|
fixture_path = File.join(Dir.pwd, 'spec', 'fixtures')
|
11
11
|
|
12
12
|
config.before(:each) do
|
13
|
+
# Reset cart state between tests
|
14
|
+
Dugway.instance_variable_set(:@cart, nil)
|
15
|
+
|
13
16
|
# Stub Dugway.logger to write to /dev/null before each test
|
14
17
|
# This prevents log file creation/modification by the test suite.
|
15
18
|
allow(Dugway).to receive(:logger).and_return(Logger.new(File::NULL))
|