theme-check 1.5.1 → 1.6.2
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/theme-check.yml +12 -4
- data/CHANGELOG.md +35 -0
- data/docs/api/html_check.md +7 -7
- data/docs/api/liquid_check.md +10 -10
- data/docs/checks/convert_include_to_render.md +1 -1
- data/docs/checks/missing_enable_comment.md +1 -1
- data/lib/theme_check/analyzer.rb +20 -15
- data/lib/theme_check/asset_file.rb +13 -2
- data/lib/theme_check/check.rb +3 -3
- data/lib/theme_check/checks/asset_size_css.rb +15 -0
- data/lib/theme_check/checks/asset_size_css_stylesheet_tag.rb +18 -1
- data/lib/theme_check/checks/convert_include_to_render.rb +2 -1
- data/lib/theme_check/checks/html_parsing_error.rb +2 -2
- data/lib/theme_check/checks/liquid_tag.rb +1 -1
- data/lib/theme_check/checks/matching_translations.rb +1 -1
- data/lib/theme_check/checks/missing_required_template_files.rb +21 -7
- data/lib/theme_check/checks/missing_template.rb +6 -6
- data/lib/theme_check/checks/nested_snippet.rb +2 -2
- data/lib/theme_check/checks/required_directories.rb +3 -1
- data/lib/theme_check/checks/required_layout_theme_object.rb +2 -2
- data/lib/theme_check/checks/space_inside_braces.rb +47 -24
- data/lib/theme_check/checks/syntax_error.rb +5 -5
- data/lib/theme_check/checks/template_length.rb +2 -2
- data/lib/theme_check/checks/translation_key_exists.rb +3 -1
- data/lib/theme_check/checks/undefined_object.rb +7 -7
- data/lib/theme_check/checks/unused_assign.rb +4 -4
- data/lib/theme_check/checks/unused_snippet.rb +8 -6
- data/lib/theme_check/checks/valid_json.rb +1 -1
- data/lib/theme_check/checks.rb +4 -2
- data/lib/theme_check/cli.rb +7 -4
- data/lib/theme_check/corrector.rb +25 -12
- data/lib/theme_check/disabled_check.rb +3 -3
- data/lib/theme_check/disabled_checks.rb +9 -9
- data/lib/theme_check/file_system_storage.rb +13 -2
- data/lib/theme_check/html_node.rb +40 -32
- data/lib/theme_check/html_visitor.rb +24 -12
- data/lib/theme_check/in_memory_storage.rb +9 -1
- data/lib/theme_check/json_check.rb +2 -2
- data/lib/theme_check/json_file.rb +9 -4
- data/lib/theme_check/language_server/diagnostics_tracker.rb +8 -8
- data/lib/theme_check/{template.rb → liquid_file.rb} +6 -20
- data/lib/theme_check/liquid_node.rb +291 -0
- data/lib/theme_check/{visitor.rb → liquid_visitor.rb} +4 -4
- data/lib/theme_check/locale_diff.rb +5 -5
- data/lib/theme_check/node.rb +12 -118
- data/lib/theme_check/offense.rb +41 -15
- data/lib/theme_check/position.rb +28 -17
- data/lib/theme_check/position_helper.rb +13 -15
- data/lib/theme_check/regex_helpers.rb +1 -15
- data/lib/theme_check/remote_asset_file.rb +4 -0
- data/lib/theme_check/theme.rb +1 -1
- data/lib/theme_check/theme_file.rb +18 -1
- data/lib/theme_check/theme_file_rewriter.rb +57 -0
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check.rb +11 -9
- data/theme-check.gemspec +2 -1
- metadata +22 -6
@@ -26,12 +26,12 @@ module ThemeCheck
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def on_document(node)
|
29
|
-
@templates[node.
|
29
|
+
@templates[node.theme_file.name] = TemplateInfo.new(Set.new)
|
30
30
|
end
|
31
31
|
|
32
32
|
def on_include(node)
|
33
33
|
if node.value.template_name_expr.is_a?(String)
|
34
|
-
@templates[node.
|
34
|
+
@templates[node.theme_file.name].includes << node
|
35
35
|
end
|
36
36
|
end
|
37
37
|
alias_method :on_render, :on_include
|
@@ -18,7 +18,9 @@ module ThemeCheck
|
|
18
18
|
private
|
19
19
|
|
20
20
|
def add_missing_directories_offense(directory)
|
21
|
-
add_offense("Theme is missing '#{directory}' directory")
|
21
|
+
add_offense("Theme is missing '#{directory}' directory") do |corrector|
|
22
|
+
corrector.mkdir(@theme, directory)
|
23
|
+
end
|
22
24
|
end
|
23
25
|
end
|
24
26
|
end
|
@@ -14,7 +14,7 @@ module ThemeCheck
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def on_document(node)
|
17
|
-
@layout_theme_node = node if node.
|
17
|
+
@layout_theme_node = node if node.theme_file.name == LAYOUT_FILENAME
|
18
18
|
end
|
19
19
|
|
20
20
|
def on_variable(node)
|
@@ -25,7 +25,7 @@ module ThemeCheck
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def after_document(node)
|
28
|
-
return unless node.
|
28
|
+
return unless node.theme_file.name == LAYOUT_FILENAME
|
29
29
|
|
30
30
|
add_missing_object_offense("content_for_layout") unless @content_for_layout_found
|
31
31
|
add_missing_object_offense("content_for_header") unless @content_for_header_found
|
@@ -15,52 +15,57 @@ module ThemeCheck
|
|
15
15
|
return if :assign == node.type_name
|
16
16
|
|
17
17
|
outside_of_strings(node.markup) do |chunk, chunk_start|
|
18
|
-
chunk.scan(/([,:|]|==|<>|<=|>=|<|>|!=)
|
18
|
+
chunk.scan(/([,:|]|==|<>|<=|>=|<|>|!=)( +)/) do |_match|
|
19
19
|
add_offense(
|
20
20
|
"Too many spaces after '#{Regexp.last_match(1)}'",
|
21
21
|
node: node,
|
22
|
-
markup: Regexp.last_match(
|
23
|
-
node_markup_offset: chunk_start + Regexp.last_match.begin(
|
22
|
+
markup: Regexp.last_match(2),
|
23
|
+
node_markup_offset: chunk_start + Regexp.last_match.begin(2)
|
24
24
|
)
|
25
25
|
end
|
26
26
|
chunk.scan(/([,:|]|==|<>|<=|>=|<\b|>\b|!=)(\S|\z)/) do |_match|
|
27
27
|
add_offense(
|
28
28
|
"Space missing after '#{Regexp.last_match(1)}'",
|
29
29
|
node: node,
|
30
|
-
markup: Regexp.last_match(
|
30
|
+
markup: Regexp.last_match(1),
|
31
31
|
node_markup_offset: chunk_start + Regexp.last_match.begin(0),
|
32
32
|
)
|
33
33
|
end
|
34
|
-
chunk.scan(/ (\||==|<>|<=|>=|<|>|!=)+/) do |_match|
|
34
|
+
chunk.scan(/( +)(\||==|<>|<=|>=|<|>|!=)+/) do |_match|
|
35
35
|
add_offense(
|
36
|
-
"Too many spaces before '#{Regexp.last_match(
|
36
|
+
"Too many spaces before '#{Regexp.last_match(2)}'",
|
37
37
|
node: node,
|
38
|
-
markup: Regexp.last_match(
|
39
|
-
node_markup_offset: chunk_start + Regexp.last_match.begin(
|
38
|
+
markup: Regexp.last_match(1),
|
39
|
+
node_markup_offset: chunk_start + Regexp.last_match.begin(1)
|
40
40
|
)
|
41
41
|
end
|
42
42
|
chunk.scan(/(\A|\S)(?<match>\||==|<>|<=|>=|<|\b>|!=)/) do |_match|
|
43
43
|
add_offense(
|
44
44
|
"Space missing before '#{Regexp.last_match(1)}'",
|
45
45
|
node: node,
|
46
|
-
markup: Regexp.last_match(
|
47
|
-
node_markup_offset: chunk_start + Regexp.last_match.begin(
|
46
|
+
markup: Regexp.last_match(:match),
|
47
|
+
node_markup_offset: chunk_start + Regexp.last_match.begin(:match)
|
48
48
|
)
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
53
|
def on_tag(node)
|
54
|
-
|
55
|
-
markup = if node.whitespace_trimmed?
|
56
|
-
"-%}"
|
57
|
-
else
|
58
|
-
"%}"
|
59
|
-
end
|
54
|
+
unless node.inside_liquid_tag?
|
60
55
|
if node.markup[-1] != " " && node.markup[-1] != "\n"
|
61
|
-
add_offense(
|
56
|
+
add_offense(
|
57
|
+
"Space missing before '#{node.end_token}'",
|
58
|
+
node: node,
|
59
|
+
markup: node.markup[-1],
|
60
|
+
node_markup_offset: node.markup.size - 1,
|
61
|
+
)
|
62
62
|
elsif node.markup =~ /(\n?)( +)\z/m && Regexp.last_match(1) != "\n"
|
63
|
-
add_offense(
|
63
|
+
add_offense(
|
64
|
+
"Too many spaces before '#{node.end_token}'",
|
65
|
+
node: node,
|
66
|
+
markup: Regexp.last_match(2),
|
67
|
+
node_markup_offset: node.markup.size - Regexp.last_match(2).size
|
68
|
+
)
|
64
69
|
end
|
65
70
|
end
|
66
71
|
@ignore = true
|
@@ -73,22 +78,40 @@ module ThemeCheck
|
|
73
78
|
def on_variable(node)
|
74
79
|
return if @ignore || node.markup.empty?
|
75
80
|
if node.markup[0] != " "
|
76
|
-
add_offense(
|
81
|
+
add_offense(
|
82
|
+
"Space missing after '#{node.start_token}'",
|
83
|
+
node: node,
|
84
|
+
markup: node.markup[0]
|
85
|
+
) do |corrector|
|
77
86
|
corrector.insert_before(node, " ")
|
78
87
|
end
|
79
88
|
end
|
80
89
|
if node.markup[-1] != " " && node.markup[-1] != "\n"
|
81
|
-
add_offense(
|
90
|
+
add_offense(
|
91
|
+
"Space missing before '#{node.end_token}'",
|
92
|
+
node: node,
|
93
|
+
markup: node.markup[-1],
|
94
|
+
node_markup_offset: node.markup.size - 1,
|
95
|
+
) do |corrector|
|
82
96
|
corrector.insert_after(node, " ")
|
83
97
|
end
|
84
98
|
end
|
85
|
-
if node.markup
|
86
|
-
add_offense(
|
99
|
+
if node.markup =~ /\A( +)/m
|
100
|
+
add_offense(
|
101
|
+
"Too many spaces after '#{node.start_token}'",
|
102
|
+
node: node,
|
103
|
+
markup: Regexp.last_match(1),
|
104
|
+
) do |corrector|
|
87
105
|
corrector.replace(node, " #{node.markup.lstrip}")
|
88
106
|
end
|
89
107
|
end
|
90
|
-
if node.markup
|
91
|
-
add_offense(
|
108
|
+
if node.markup =~ /(\n?)( +)\z/m && Regexp.last_match(1) != "\n"
|
109
|
+
add_offense(
|
110
|
+
"Too many spaces before '#{node.end_token}'",
|
111
|
+
node: node,
|
112
|
+
markup: Regexp.last_match(2),
|
113
|
+
node_markup_offset: node.markup.size - Regexp.last_match(2).size
|
114
|
+
) do |corrector|
|
92
115
|
corrector.replace(node, "#{node.markup.rstrip} ")
|
93
116
|
end
|
94
117
|
end
|
@@ -7,23 +7,23 @@ module ThemeCheck
|
|
7
7
|
doc docs_url(__FILE__)
|
8
8
|
|
9
9
|
def on_document(node)
|
10
|
-
node.
|
11
|
-
add_exception_as_offense(warning,
|
10
|
+
node.theme_file.warnings.each do |warning|
|
11
|
+
add_exception_as_offense(warning, theme_file: node.theme_file)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
15
|
def on_error(exception)
|
16
|
-
add_exception_as_offense(exception,
|
16
|
+
add_exception_as_offense(exception, theme_file: theme[exception.template_name])
|
17
17
|
end
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
def add_exception_as_offense(exception,
|
21
|
+
def add_exception_as_offense(exception, theme_file:)
|
22
22
|
add_offense(
|
23
23
|
exception.to_s(false).sub(/ in ".*"$/, ''),
|
24
24
|
line_number: exception.line_number,
|
25
25
|
markup: exception.markup_context&.sub(/^in "(.*)"$/, '\1'),
|
26
|
-
|
26
|
+
theme_file: theme_file,
|
27
27
|
)
|
28
28
|
end
|
29
29
|
end
|
@@ -29,9 +29,9 @@ module ThemeCheck
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def after_document(node)
|
32
|
-
lines = node.
|
32
|
+
lines = node.theme_file.source.count("\n") - @excluded_lines
|
33
33
|
if lines > @max_length
|
34
|
-
add_offense("Template has too many lines [#{lines}/#{@max_length}]",
|
34
|
+
add_offense("Template has too many lines [#{lines}/#{@max_length}]", theme_file: node.theme_file)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
@@ -29,7 +29,9 @@ module ThemeCheck
|
|
29
29
|
"'#{key_node.value}' does not have a matching entry in '#{@theme.default_locale_json.relative_path}'",
|
30
30
|
node: node,
|
31
31
|
markup: key_node.value,
|
32
|
-
)
|
32
|
+
) do |corrector|
|
33
|
+
corrector.add_default_translation_key(@theme.default_locale_json, key_node.value.split("."), "TODO")
|
34
|
+
end
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
@@ -62,22 +62,22 @@ module ThemeCheck
|
|
62
62
|
|
63
63
|
def on_document(node)
|
64
64
|
return if ignore?(node)
|
65
|
-
@files[node.
|
65
|
+
@files[node.theme_file.name] = TemplateInfo.new
|
66
66
|
end
|
67
67
|
|
68
68
|
def on_assign(node)
|
69
69
|
return if ignore?(node)
|
70
|
-
@files[node.
|
70
|
+
@files[node.theme_file.name].all_assigns[node.value.to] = node
|
71
71
|
end
|
72
72
|
|
73
73
|
def on_capture(node)
|
74
74
|
return if ignore?(node)
|
75
|
-
@files[node.
|
75
|
+
@files[node.theme_file.name].all_captures[node.value.instance_variable_get('@to')] = node
|
76
76
|
end
|
77
77
|
|
78
78
|
def on_for(node)
|
79
79
|
return if ignore?(node)
|
80
|
-
@files[node.
|
80
|
+
@files[node.theme_file.name].all_forloops[node.value.variable_name] = node
|
81
81
|
end
|
82
82
|
|
83
83
|
def on_include(_node)
|
@@ -90,7 +90,7 @@ module ThemeCheck
|
|
90
90
|
return unless node.value.template_name_expr.is_a?(String)
|
91
91
|
|
92
92
|
snippet_name = "snippets/#{node.value.template_name_expr}"
|
93
|
-
@files[node.
|
93
|
+
@files[node.theme_file.name].add_render(
|
94
94
|
name: snippet_name,
|
95
95
|
node: node,
|
96
96
|
)
|
@@ -98,7 +98,7 @@ module ThemeCheck
|
|
98
98
|
|
99
99
|
def on_variable_lookup(node)
|
100
100
|
return if ignore?(node)
|
101
|
-
@files[node.
|
101
|
+
@files[node.theme_file.name].add_variable_lookup(
|
102
102
|
name: node.value.name,
|
103
103
|
node: node,
|
104
104
|
)
|
@@ -130,7 +130,7 @@ module ThemeCheck
|
|
130
130
|
private
|
131
131
|
|
132
132
|
def ignore?(node)
|
133
|
-
@exclude_snippets && node.
|
133
|
+
@exclude_snippets && node.theme_file.snippet?
|
134
134
|
end
|
135
135
|
|
136
136
|
def each_template
|
@@ -25,21 +25,21 @@ module ThemeCheck
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def on_document(node)
|
28
|
-
@templates[node.
|
28
|
+
@templates[node.theme_file.name] = TemplateInfo.new(Set.new, {}, Set.new)
|
29
29
|
end
|
30
30
|
|
31
31
|
def on_assign(node)
|
32
|
-
@templates[node.
|
32
|
+
@templates[node.theme_file.name].assign_nodes[node.value.to] = node
|
33
33
|
end
|
34
34
|
|
35
35
|
def on_include(node)
|
36
36
|
if node.value.template_name_expr.is_a?(String)
|
37
|
-
@templates[node.
|
37
|
+
@templates[node.theme_file.name].includes << "snippets/#{node.value.template_name_expr}"
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
41
|
def on_variable_lookup(node)
|
42
|
-
@templates[node.
|
42
|
+
@templates[node.theme_file.name].used_assigns << node.value.name
|
43
43
|
end
|
44
44
|
|
45
45
|
def on_end
|
@@ -8,28 +8,30 @@ module ThemeCheck
|
|
8
8
|
doc docs_url(__FILE__)
|
9
9
|
|
10
10
|
def initialize
|
11
|
-
@
|
11
|
+
@used_snippets = Set.new
|
12
12
|
end
|
13
13
|
|
14
14
|
def on_include(node)
|
15
15
|
if node.value.template_name_expr.is_a?(String)
|
16
|
-
@
|
16
|
+
@used_snippets << "snippets/#{node.value.template_name_expr}"
|
17
17
|
else
|
18
18
|
# Can't reliably track unused snippets if an expression is used, ignore this check
|
19
|
-
@
|
19
|
+
@used_snippets.clear
|
20
20
|
ignore!
|
21
21
|
end
|
22
22
|
end
|
23
23
|
alias_method :on_render, :on_include
|
24
24
|
|
25
25
|
def on_end
|
26
|
-
missing_snippets.each do |
|
27
|
-
add_offense("This
|
26
|
+
missing_snippets.each do |theme_file|
|
27
|
+
add_offense("This snippet is not used", theme_file: theme_file) do |corrector|
|
28
|
+
corrector.remove(@theme, theme_file.relative_path.to_s)
|
29
|
+
end
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
31
33
|
def missing_snippets
|
32
|
-
theme.snippets.reject { |t| @
|
34
|
+
theme.snippets.reject { |t| @used_snippets.include?(t.name) }
|
33
35
|
end
|
34
36
|
end
|
35
37
|
end
|
data/lib/theme_check/checks.rb
CHANGED
@@ -47,9 +47,10 @@ module ThemeCheck
|
|
47
47
|
raise
|
48
48
|
rescue => e
|
49
49
|
node = args.first
|
50
|
-
|
50
|
+
theme_file = node.respond_to?(:theme_file) ? node.theme_file.relative_path : "?"
|
51
51
|
markup = node.respond_to?(:markup) ? node.markup : ""
|
52
52
|
node_class = node.respond_to?(:value) ? node.value.class : "?"
|
53
|
+
line_number = node.respond_to?(:line_number) ? node.line_number : "?"
|
53
54
|
|
54
55
|
ThemeCheck.bug(<<~EOS)
|
55
56
|
Exception while running `#{check.code_name}##{method}`:
|
@@ -58,12 +59,13 @@ module ThemeCheck
|
|
58
59
|
#{e.backtrace.join("\n ")}
|
59
60
|
```
|
60
61
|
|
61
|
-
|
62
|
+
Theme File: `#{theme_file}`
|
62
63
|
Node: `#{node_class}`
|
63
64
|
Markup:
|
64
65
|
```
|
65
66
|
#{markup}
|
66
67
|
```
|
68
|
+
Line number: #{line_number}
|
67
69
|
Check options: `#{check.options.pretty_inspect}`
|
68
70
|
EOS
|
69
71
|
end
|
data/lib/theme_check/cli.rb
CHANGED
@@ -49,7 +49,7 @@ module ThemeCheck
|
|
49
49
|
"Automatically fix offenses"
|
50
50
|
) { @auto_correct = true }
|
51
51
|
@option_parser.on(
|
52
|
-
"--fail-level SEVERITY", Check::SEVERITIES,
|
52
|
+
"--fail-level SEVERITY", [:crash] + Check::SEVERITIES,
|
53
53
|
"Minimum severity (error|suggestion|style) for exit with error code"
|
54
54
|
) do |severity|
|
55
55
|
@fail_level = severity.to_sym
|
@@ -186,12 +186,15 @@ module ThemeCheck
|
|
186
186
|
storage = ThemeCheck::FileSystemStorage.new(@config.root, ignored_patterns: @config.ignored_patterns)
|
187
187
|
theme = ThemeCheck::Theme.new(storage)
|
188
188
|
if theme.all.empty?
|
189
|
-
raise Abort, "No
|
189
|
+
raise Abort, "No theme files found."
|
190
190
|
end
|
191
191
|
analyzer = ThemeCheck::Analyzer.new(theme, @config.enabled_checks, @config.auto_correct)
|
192
192
|
analyzer.analyze_theme
|
193
193
|
analyzer.correct_offenses
|
194
|
-
|
194
|
+
print_with_format(theme, analyzer, out_stream)
|
195
|
+
# corrections are committed after printing so that the
|
196
|
+
# source_excerpts are still pointing to the uncorrected source.
|
197
|
+
analyzer.write_corrections
|
195
198
|
raise Abort, "" if analyzer.uncorrectable_offenses.any? do |offense|
|
196
199
|
offense.check.severity_value <= Check.severity_value(@fail_level)
|
197
200
|
end
|
@@ -211,7 +214,7 @@ module ThemeCheck
|
|
211
214
|
STDERR.puts "Profiling is only available in development"
|
212
215
|
end
|
213
216
|
|
214
|
-
def
|
217
|
+
def print_with_format(theme, analyzer, out_stream)
|
215
218
|
case @format
|
216
219
|
when :text
|
217
220
|
ThemeCheck::Printer.new(out_stream).print(theme, analyzer.offenses, @config.auto_correct)
|
@@ -2,30 +2,25 @@
|
|
2
2
|
|
3
3
|
module ThemeCheck
|
4
4
|
class Corrector
|
5
|
-
def initialize(
|
6
|
-
@
|
5
|
+
def initialize(theme_file:)
|
6
|
+
@theme_file = theme_file
|
7
7
|
end
|
8
8
|
|
9
9
|
def insert_after(node, content)
|
10
|
-
|
11
|
-
line.insert(node.range[1] + 1, content)
|
10
|
+
@theme_file.rewriter.insert_after(node, content)
|
12
11
|
end
|
13
12
|
|
14
13
|
def insert_before(node, content)
|
15
|
-
|
16
|
-
line.insert(node.range[0], content)
|
14
|
+
@theme_file.rewriter.insert_before(node, content)
|
17
15
|
end
|
18
16
|
|
19
17
|
def replace(node, content)
|
20
|
-
|
21
|
-
line[node.range[0]..node.range[1]] = content
|
18
|
+
@theme_file.rewriter.replace(node, content)
|
22
19
|
node.markup = content
|
23
20
|
end
|
24
21
|
|
25
22
|
def wrap(node, insert_before, insert_after)
|
26
|
-
|
27
|
-
line.insert(node.range[0], insert_before)
|
28
|
-
line.insert(node.range[1] + 1 + insert_before.length, insert_after)
|
23
|
+
@theme_file.rewriter.wrap(node, insert_before, insert_after)
|
29
24
|
end
|
30
25
|
|
31
26
|
def create(theme, relative_path, content)
|
@@ -34,7 +29,25 @@ module ThemeCheck
|
|
34
29
|
|
35
30
|
def create_default_locale_json(theme)
|
36
31
|
theme.default_locale_json = JsonFile.new("locales/#{theme.default_locale}.default.json", theme.storage)
|
37
|
-
theme.default_locale_json.update_contents(
|
32
|
+
theme.default_locale_json.update_contents({})
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove(theme, relative_path)
|
36
|
+
theme.storage.remove(relative_path)
|
37
|
+
end
|
38
|
+
|
39
|
+
def mkdir(theme, relative_path)
|
40
|
+
theme.storage.mkdir(relative_path)
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_default_translation_key(file, key, value)
|
44
|
+
hash = file.content
|
45
|
+
key.reduce(hash) do |pointer, token|
|
46
|
+
return pointer[token] = value if token == key.last
|
47
|
+
pointer[token] = {} unless pointer.key?(token)
|
48
|
+
pointer[token]
|
49
|
+
end
|
50
|
+
file.update_contents(hash)
|
38
51
|
end
|
39
52
|
end
|
40
53
|
end
|