dialecto-rails 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +196 -0
- data/action.yml +83 -0
- data/exe/dialecto-rails +6 -0
- data/lib/dialecto-rails.rb +1 -0
- data/lib/dialecto_rails/config.rb +206 -0
- data/lib/dialecto_rails/errors.rb +21 -0
- data/lib/dialecto_rails/in_context/args.rb +37 -0
- data/lib/dialecto_rails/in_context/assets/overlay.js +2784 -0
- data/lib/dialecto_rails/in_context/backend_hook.rb +39 -0
- data/lib/dialecto_rails/in_context/csp.rb +72 -0
- data/lib/dialecto_rails/in_context/git_context.rb +103 -0
- data/lib/dialecto_rails/in_context/key_index.rb +128 -0
- data/lib/dialecto_rails/in_context/launch_token.rb +78 -0
- data/lib/dialecto_rails/in_context/live_middleware.rb +307 -0
- data/lib/dialecto_rails/in_context/live_runtime.rb +53 -0
- data/lib/dialecto_rails/in_context/live_store.rb +14 -0
- data/lib/dialecto_rails/in_context/marker.rb +106 -0
- data/lib/dialecto_rails/in_context/marking.rb +122 -0
- data/lib/dialecto_rails/in_context/middleware.rb +202 -0
- data/lib/dialecto_rails/in_context/overrides.rb +61 -0
- data/lib/dialecto_rails/in_context/railtie_stub.rb +76 -0
- data/lib/dialecto_rails/in_context/runtime.rb +52 -0
- data/lib/dialecto_rails/in_context/sanitizer.rb +19 -0
- data/lib/dialecto_rails/in_context/store.rb +41 -0
- data/lib/dialecto_rails/scan/catalogs.rb +46 -0
- data/lib/dialecto_rails/scan/cli.rb +339 -0
- data/lib/dialecto_rails/scan/client.rb +117 -0
- data/lib/dialecto_rails/scan/envelope.rb +100 -0
- data/lib/dialecto_rails/scan/git.rb +68 -0
- data/lib/dialecto_rails/scan/worktree.rb +41 -0
- data/lib/dialecto_rails/unmarked.rb +42 -0
- data/lib/dialecto_rails/usages/call_args.rb +78 -0
- data/lib/dialecto_rails/usages/catalog_keys.rb +67 -0
- data/lib/dialecto_rails/usages/check.rb +70 -0
- data/lib/dialecto_rails/usages/haml_extractor.rb +260 -0
- data/lib/dialecto_rails/usages/keys.rb +206 -0
- data/lib/dialecto_rails/usages/known_wrappers.rb +31 -0
- data/lib/dialecto_rails/usages/lazy_scope.rb +52 -0
- data/lib/dialecto_rails/usages/parse_check.rb +75 -0
- data/lib/dialecto_rails/usages/read_by_name.json +28 -0
- data/lib/dialecto_rails/usages/record.rb +91 -0
- data/lib/dialecto_rails/usages/ruby_visitor.rb +628 -0
- data/lib/dialecto_rails/usages/scanner.rb +241 -0
- data/lib/dialecto_rails/usages/summary.rb +191 -0
- data/lib/dialecto_rails/usages/templates.rb +45 -0
- data/lib/dialecto_rails/usages/wrappers.rb +336 -0
- data/lib/dialecto_rails/version.rb +3 -0
- data/lib/dialecto_rails.rb +14 -0
- metadata +119 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
require "prism"
|
|
2
|
+
require_relative "keys"
|
|
3
|
+
require_relative "call_args"
|
|
4
|
+
|
|
5
|
+
module DialectoRails
|
|
6
|
+
module Usages
|
|
7
|
+
# Wrapper methods (spec-0023 §6.3): a method whose parameter flows into a translate call's key
|
|
8
|
+
# (`def tt(key) = t("nav.#{key}")`). The call inside the wrapper is not a use of its own: its literal head
|
|
9
|
+
# becomes a `template` record, and each call to the wrapper becomes a use. Found by repeating until nothing
|
|
10
|
+
# new is found, so a wrapper of a wrapper counts.
|
|
11
|
+
class Wrappers
|
|
12
|
+
# items: `{lit:}`, `{wild: true}` and `{param: index}`; templates: ids of the template records that hold
|
|
13
|
+
# the text; opaque: the key can't be worked out (an option such as `scope:` changes it).
|
|
14
|
+
# fixed: the key prefix a declared wrapper adds on its own (`Spree.t` reads under `spree.`), for a call whose
|
|
15
|
+
# key can't be worked out (it is then a pattern under that prefix, not under everything).
|
|
16
|
+
Builder = Struct.new(:items, :templates, :opaque, :fixed, keyword_init: true) do
|
|
17
|
+
def pass_through? = !opaque && items.length == 1 && items.first.key?(:param)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# One translate-like call inside a method, whose key mentions the method's parameters.
|
|
21
|
+
Call = Struct.new(:offset, :name, :kind, :items, :args, :opaque, :template, :template_id, keyword_init: true)
|
|
22
|
+
|
|
23
|
+
# `owner`: the class or module the method sits in (`{name:, module:}`), nil at the top level. `receiver`: the
|
|
24
|
+
# constant it is called on (`Spree` for `def self.t`, `class << self` or `module_function`), else nil.
|
|
25
|
+
Definition = Struct.new(:file, :offset, :name, :calls, :builders, :owner, :receiver, keyword_init: true)
|
|
26
|
+
|
|
27
|
+
# Where a template record is written: the key literal's start up to its first interpolation.
|
|
28
|
+
Template = Struct.new(:start, :text_end, :literal, :id, keyword_init: true)
|
|
29
|
+
|
|
30
|
+
# `declared`: methods called on a constant that the scan can't read the body of (a gem's `Spree.t`): hashes
|
|
31
|
+
# of `receiver:` ("Spree"), `name:` ("t") and `scope:` ("spree" or nil). They take the key as their first
|
|
32
|
+
# argument, like `t`, and put `scope` in front of it.
|
|
33
|
+
def initialize(i18n_functions: [], declared: [])
|
|
34
|
+
@i18n_functions = i18n_functions.map(&:to_sym)
|
|
35
|
+
@definitions = []
|
|
36
|
+
@table = {}
|
|
37
|
+
@by_name = {}
|
|
38
|
+
@parents = {}
|
|
39
|
+
@aliases = []
|
|
40
|
+
@declared = declared.map { |entry| [entry[:receiver], entry[:name].to_sym, declared_builder(entry[:scope])] }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def scout(file, program)
|
|
44
|
+
Scout.new(file, @i18n_functions, @definitions, @parents, @aliases).visit(program)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The builders of a wrapper called on a constant (`Spree.t("x")`, `::Spree.t`), or nil. A declared wrapper
|
|
48
|
+
# matches the constant as written; one found in the source matches by the constant's last name.
|
|
49
|
+
def lookup_receiver(path, name)
|
|
50
|
+
name = name.to_sym
|
|
51
|
+
builders = @declared.filter_map { |receiver, declared, builder| builder if receiver == path && declared == name }
|
|
52
|
+
last = path.split("::").last
|
|
53
|
+
(@by_name[name] || []).each { |definition, builder| builders << builder if definition.receiver == last }
|
|
54
|
+
builders.uniq!
|
|
55
|
+
builders.empty? ? nil : builders
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def resolve!
|
|
59
|
+
expand_aliases
|
|
60
|
+
10.times do
|
|
61
|
+
changed = false
|
|
62
|
+
@definitions.each do |definition|
|
|
63
|
+
builders = definition.calls.filter_map { |call| builder_for(call, definition) }.uniq
|
|
64
|
+
next if builders.empty? || builders == definition.builders
|
|
65
|
+
|
|
66
|
+
definition.builders = builders
|
|
67
|
+
changed = true
|
|
68
|
+
end
|
|
69
|
+
rebuild_table
|
|
70
|
+
break unless changed
|
|
71
|
+
end
|
|
72
|
+
self
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# The builders of the wrappers a call can reach, or nil. A bare call reaches a method of its own file, of a
|
|
76
|
+
# module (a helper or concern mixes in anywhere), of a class it descends from, or a top-level one. Without
|
|
77
|
+
# this, `validate :x` in any model would match some unrelated `def validate(key)`. Several different
|
|
78
|
+
# builders make the name ambiguous.
|
|
79
|
+
def lookup(name, file:, klass: nil)
|
|
80
|
+
reachable = (@by_name[name.to_sym] || []).select { |definition, _| reaches?(definition, file, klass) }
|
|
81
|
+
builders = reachable.map(&:last).uniq
|
|
82
|
+
builders.empty? ? nil : builders
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def any? = !@table.empty?
|
|
86
|
+
|
|
87
|
+
# [file, call offset] → the template that call writes, or false when it has none: the call is suppressed.
|
|
88
|
+
def suppressed(file, offset)
|
|
89
|
+
@suppressed ||= build_suppressed
|
|
90
|
+
@suppressed[[file, offset]]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def declared_builder(scope)
|
|
96
|
+
prefix = Array(scope).map(&:to_s).reject(&:empty?).join(".")
|
|
97
|
+
return Builder.new(items: [{param: 0}], templates: [], opaque: false) if prefix.empty?
|
|
98
|
+
|
|
99
|
+
Builder.new(items: [{lit: "#{prefix}."}, {param: 0}], templates: [], opaque: false, fixed: "#{prefix}.")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# `alias t translate` and `alias_method :t, :translate` make a second name for a wrapper.
|
|
103
|
+
def expand_aliases
|
|
104
|
+
return if @aliases_done
|
|
105
|
+
|
|
106
|
+
@aliases_done = true
|
|
107
|
+
@aliases.each do |entry|
|
|
108
|
+
@definitions.dup.each do |definition|
|
|
109
|
+
next unless definition.file == entry[:file] && definition.name == entry[:old] &&
|
|
110
|
+
definition.owner&.dig(:name) == entry[:owner]&.dig(:name)
|
|
111
|
+
|
|
112
|
+
copy = definition.dup
|
|
113
|
+
copy.name = entry[:new]
|
|
114
|
+
copy.receiver = entry[:owner]&.dig(:name) if entry[:singleton]
|
|
115
|
+
@definitions << copy
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def build_suppressed
|
|
121
|
+
out = {}
|
|
122
|
+
@definitions.each do |definition|
|
|
123
|
+
next if definition.builders.nil?
|
|
124
|
+
|
|
125
|
+
definition.calls.each { |call| out[[definition.file, call.offset]] = call.template || false }
|
|
126
|
+
end
|
|
127
|
+
out
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def rebuild_table
|
|
131
|
+
@table = {}
|
|
132
|
+
@by_name = {}
|
|
133
|
+
@definitions.each do |definition|
|
|
134
|
+
next if definition.builders.nil? || definition.builders.empty?
|
|
135
|
+
|
|
136
|
+
(@table[definition.name.to_sym] ||= []).concat(definition.builders)
|
|
137
|
+
definition.builders.each { |builder| (@by_name[definition.name.to_sym] ||= []) << [definition, builder] }
|
|
138
|
+
end
|
|
139
|
+
@table.each_value(&:uniq!)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def reaches?(definition, file, klass)
|
|
143
|
+
owner = definition.owner
|
|
144
|
+
return true if owner.nil? || owner[:module] || definition.file == file
|
|
145
|
+
return true if definition.file.match?(%r{(?:\A|/)app/helpers/})
|
|
146
|
+
|
|
147
|
+
ancestors?(klass, owner[:name])
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Does `klass` (a simple class name) descend from `owner`, by the superclasses seen in the scan?
|
|
151
|
+
def ancestors?(klass, owner)
|
|
152
|
+
seen = {}
|
|
153
|
+
while klass && !seen[klass]
|
|
154
|
+
return true if klass == owner
|
|
155
|
+
|
|
156
|
+
seen[klass] = true
|
|
157
|
+
klass = @parents[klass]
|
|
158
|
+
end
|
|
159
|
+
false
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def builder_for(call, definition)
|
|
163
|
+
if call.kind == :builtin
|
|
164
|
+
return Builder.new(items: call.items, templates: [call.template_id].compact, opaque: true) if call.opaque
|
|
165
|
+
|
|
166
|
+
return Builder.new(items: call.items, templates: [call.template_id].compact, opaque: false)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
return nil if definition.name.to_sym == call.name.to_sym
|
|
170
|
+
|
|
171
|
+
callee = lookup(call.name, file: definition.file, klass: definition.owner&.dig(:name))
|
|
172
|
+
return nil if callee.nil?
|
|
173
|
+
|
|
174
|
+
if callee.length != 1 || callee.first.opaque || call.opaque
|
|
175
|
+
return Builder.new(items: [{wild: true}], templates: [], opaque: true)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
built = callee.first
|
|
179
|
+
items = built.items.flat_map do |item|
|
|
180
|
+
item.key?(:param) ? (call.args[item[:param]] || [{wild: true}]) : [item]
|
|
181
|
+
end
|
|
182
|
+
Builder.new(items: items, templates: ([call.template_id] + built.templates).compact, opaque: false)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Walks a file's methods, noting the translate-like calls whose key mentions a parameter.
|
|
186
|
+
class Scout < Prism::Visitor
|
|
187
|
+
def initialize(file, i18n_functions, definitions, parents, aliases)
|
|
188
|
+
super()
|
|
189
|
+
@file = file
|
|
190
|
+
@i18n_functions = i18n_functions
|
|
191
|
+
@definitions = definitions
|
|
192
|
+
@parents = parents
|
|
193
|
+
@aliases = aliases
|
|
194
|
+
@frames = []
|
|
195
|
+
@owners = []
|
|
196
|
+
@singleton = 0
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# `class << self` makes every method in it callable on the class.
|
|
200
|
+
def visit_singleton_class_node(node)
|
|
201
|
+
return super unless node.expression.is_a?(Prism::SelfNode)
|
|
202
|
+
|
|
203
|
+
@singleton += 1
|
|
204
|
+
super
|
|
205
|
+
ensure
|
|
206
|
+
@singleton -= 1 if node.expression.is_a?(Prism::SelfNode)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def visit_alias_method_node(node)
|
|
210
|
+
note_alias(node.new_name, node.old_name)
|
|
211
|
+
super
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def visit_class_node(node)
|
|
215
|
+
name = node.constant_path.slice.split("::").last
|
|
216
|
+
superclass = node.superclass&.slice&.split("::")&.last
|
|
217
|
+
@parents[name] ||= superclass if superclass
|
|
218
|
+
@owners << {name: name, module: false}
|
|
219
|
+
super
|
|
220
|
+
ensure
|
|
221
|
+
@owners.pop
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def visit_module_node(node)
|
|
225
|
+
@owners << {name: node.constant_path.slice.split("::").last, module: true, functions: module_functions(node)}
|
|
226
|
+
super
|
|
227
|
+
ensure
|
|
228
|
+
@owners.pop
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def visit_def_node(node)
|
|
232
|
+
parameters = node.parameters
|
|
233
|
+
params = {}
|
|
234
|
+
positional = parameters ? parameters.requireds + parameters.optionals : []
|
|
235
|
+
positional.each_with_index do |parameter, index|
|
|
236
|
+
params[parameter.name] = index if parameter.respond_to?(:name) && parameter.name
|
|
237
|
+
end
|
|
238
|
+
forward = [parameters&.rest, parameters&.keyword_rest, parameters&.block].filter_map { |item| item.name if item.respond_to?(:name) }
|
|
239
|
+
frame = {definition: Definition.new(file: @file, offset: node.location.start_offset, name: node.name.to_s,
|
|
240
|
+
calls: [], owner: @owners.last, receiver: receiver_of(node)), params: params, forward: forward}
|
|
241
|
+
@frames << frame
|
|
242
|
+
super
|
|
243
|
+
@frames.pop
|
|
244
|
+
@definitions << frame[:definition] if !frame[:definition].calls.empty? && !params.empty?
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def visit_call_node(node)
|
|
248
|
+
frame = @frames.last
|
|
249
|
+
note(node, frame) if frame
|
|
250
|
+
note_alias(*node.arguments.arguments.first(2)) if node.name == :alias_method && node.receiver.nil? && node.arguments&.arguments&.length == 2
|
|
251
|
+
super
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
private
|
|
255
|
+
|
|
256
|
+
def note_alias(new_name, old_name)
|
|
257
|
+
names = [new_name, old_name].map { |item| item.unescaped if item.is_a?(Prism::SymbolNode) }
|
|
258
|
+
return if names.any?(&:nil?)
|
|
259
|
+
|
|
260
|
+
@aliases << {file: @file, owner: @owners.last, new: names[0], old: names[1], singleton: @singleton > 0}
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# The constant a method is called on: `def self.t`, `def Spree.t`, a method under `class << self`, or one
|
|
264
|
+
# `module_function` (or `extend self`) turns into a module function. nil for an ordinary instance method.
|
|
265
|
+
def receiver_of(node)
|
|
266
|
+
owner = @owners.last
|
|
267
|
+
case node.receiver
|
|
268
|
+
when Prism::SelfNode then owner&.dig(:name)
|
|
269
|
+
when nil
|
|
270
|
+
functions = owner&.dig(:functions)
|
|
271
|
+
callable = @singleton > 0 || functions == true || (functions.is_a?(Array) && functions.include?(node.name.to_s))
|
|
272
|
+
owner&.dig(:name) if callable
|
|
273
|
+
else CallArgs.const_path(node.receiver)&.split("::")&.last
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# `true` when the module says a bare `module_function` or `extend self`, else the names it lists.
|
|
278
|
+
def module_functions(node)
|
|
279
|
+
statements = node.body.respond_to?(:body) ? node.body.body : []
|
|
280
|
+
names = []
|
|
281
|
+
statements.each do |statement|
|
|
282
|
+
next unless statement.is_a?(Prism::CallNode) && statement.receiver.nil?
|
|
283
|
+
|
|
284
|
+
arguments = statement.arguments&.arguments || []
|
|
285
|
+
return true if statement.name == :module_function && arguments.empty?
|
|
286
|
+
return true if statement.name == :extend && arguments.first.is_a?(Prism::SelfNode)
|
|
287
|
+
|
|
288
|
+
names.concat(arguments.filter_map { |item| item.unescaped if item.is_a?(Prism::SymbolNode) }) if statement.name == :module_function
|
|
289
|
+
end
|
|
290
|
+
names.empty? ? nil : names
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def note(node, frame)
|
|
294
|
+
return if frame[:params].empty?
|
|
295
|
+
|
|
296
|
+
kind = kind_of(node)
|
|
297
|
+
return unless kind
|
|
298
|
+
|
|
299
|
+
positional = CallArgs.positional(node)
|
|
300
|
+
key_node = positional.first
|
|
301
|
+
return unless key_node
|
|
302
|
+
|
|
303
|
+
analysis = Keys.analyze(key_node, params: frame[:params])
|
|
304
|
+
return unless Array(analysis.items).any? { |item| item.key?(:param) }
|
|
305
|
+
|
|
306
|
+
options = CallArgs.options(node, forward: frame[:forward])
|
|
307
|
+
args = positional.map { |argument| Keys.analyze(argument, params: frame[:params]).items || [{wild: true}] }
|
|
308
|
+
opaque = !options.scope.nil? || options.splat || !options.separator.nil?
|
|
309
|
+
template = template_for(key_node, analysis)
|
|
310
|
+
frame[:definition].calls << Call.new(offset: node.location.start_offset, name: node.name.to_s,
|
|
311
|
+
kind: kind, items: analysis.items, args: args, opaque: opaque, template: template,
|
|
312
|
+
template_id: template&.id)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def kind_of(node)
|
|
316
|
+
receiver = CallArgs.receiver_kind(node)
|
|
317
|
+
return nil if receiver == :other
|
|
318
|
+
|
|
319
|
+
if CallArgs::TRANSLATE_NAMES.include?(node.name) || (receiver == :i18n && node.name == :exists?)
|
|
320
|
+
:builtin
|
|
321
|
+
elsif receiver == :none
|
|
322
|
+
@i18n_functions.include?(node.name) ? :builtin : :wrapper_call
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def template_for(key_node, analysis)
|
|
327
|
+
head = analysis.literals&.find(&:head?)
|
|
328
|
+
return nil unless head
|
|
329
|
+
|
|
330
|
+
start = key_node.location.start_offset
|
|
331
|
+
Template.new(start: start, text_end: head.text_end, literal: head, id: "#{@file}@#{start}")
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require_relative "dialecto_rails/version"
|
|
2
|
+
require_relative "dialecto_rails/unmarked"
|
|
3
|
+
|
|
4
|
+
# What Bundler.require loads: the version and, inside a Rails app, the Railtie stub. The scanner and the
|
|
5
|
+
# usage scan (and their native Prism and Herb) are required by the CLI only.
|
|
6
|
+
module DialectoRails
|
|
7
|
+
autoload :Config, "dialecto_rails/config"
|
|
8
|
+
autoload :Error, "dialecto_rails/errors"
|
|
9
|
+
autoload :ConfigError, "dialecto_rails/errors"
|
|
10
|
+
autoload :RefusedError, "dialecto_rails/errors"
|
|
11
|
+
autoload :RetryableError, "dialecto_rails/errors"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
require_relative "dialecto_rails/in_context/railtie_stub" if defined?(::Rails::Railtie)
|
metadata
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: dialecto-rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dialecto
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: prism
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: herb
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
description: 'The Rails side of Dialecto''s minimal-diff translation pull requests:
|
|
41
|
+
a scanner that sends config/locales to Dialecto from CI, a static scan of where
|
|
42
|
+
the code reads each key, and a development-only in-context editor.'
|
|
43
|
+
executables:
|
|
44
|
+
- dialecto-rails
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- LICENSE
|
|
49
|
+
- README.md
|
|
50
|
+
- action.yml
|
|
51
|
+
- exe/dialecto-rails
|
|
52
|
+
- lib/dialecto-rails.rb
|
|
53
|
+
- lib/dialecto_rails.rb
|
|
54
|
+
- lib/dialecto_rails/config.rb
|
|
55
|
+
- lib/dialecto_rails/errors.rb
|
|
56
|
+
- lib/dialecto_rails/in_context/args.rb
|
|
57
|
+
- lib/dialecto_rails/in_context/assets/overlay.js
|
|
58
|
+
- lib/dialecto_rails/in_context/backend_hook.rb
|
|
59
|
+
- lib/dialecto_rails/in_context/csp.rb
|
|
60
|
+
- lib/dialecto_rails/in_context/git_context.rb
|
|
61
|
+
- lib/dialecto_rails/in_context/key_index.rb
|
|
62
|
+
- lib/dialecto_rails/in_context/launch_token.rb
|
|
63
|
+
- lib/dialecto_rails/in_context/live_middleware.rb
|
|
64
|
+
- lib/dialecto_rails/in_context/live_runtime.rb
|
|
65
|
+
- lib/dialecto_rails/in_context/live_store.rb
|
|
66
|
+
- lib/dialecto_rails/in_context/marker.rb
|
|
67
|
+
- lib/dialecto_rails/in_context/marking.rb
|
|
68
|
+
- lib/dialecto_rails/in_context/middleware.rb
|
|
69
|
+
- lib/dialecto_rails/in_context/overrides.rb
|
|
70
|
+
- lib/dialecto_rails/in_context/railtie_stub.rb
|
|
71
|
+
- lib/dialecto_rails/in_context/runtime.rb
|
|
72
|
+
- lib/dialecto_rails/in_context/sanitizer.rb
|
|
73
|
+
- lib/dialecto_rails/in_context/store.rb
|
|
74
|
+
- lib/dialecto_rails/scan/catalogs.rb
|
|
75
|
+
- lib/dialecto_rails/scan/cli.rb
|
|
76
|
+
- lib/dialecto_rails/scan/client.rb
|
|
77
|
+
- lib/dialecto_rails/scan/envelope.rb
|
|
78
|
+
- lib/dialecto_rails/scan/git.rb
|
|
79
|
+
- lib/dialecto_rails/scan/worktree.rb
|
|
80
|
+
- lib/dialecto_rails/unmarked.rb
|
|
81
|
+
- lib/dialecto_rails/usages/call_args.rb
|
|
82
|
+
- lib/dialecto_rails/usages/catalog_keys.rb
|
|
83
|
+
- lib/dialecto_rails/usages/check.rb
|
|
84
|
+
- lib/dialecto_rails/usages/haml_extractor.rb
|
|
85
|
+
- lib/dialecto_rails/usages/keys.rb
|
|
86
|
+
- lib/dialecto_rails/usages/known_wrappers.rb
|
|
87
|
+
- lib/dialecto_rails/usages/lazy_scope.rb
|
|
88
|
+
- lib/dialecto_rails/usages/parse_check.rb
|
|
89
|
+
- lib/dialecto_rails/usages/read_by_name.json
|
|
90
|
+
- lib/dialecto_rails/usages/record.rb
|
|
91
|
+
- lib/dialecto_rails/usages/ruby_visitor.rb
|
|
92
|
+
- lib/dialecto_rails/usages/scanner.rb
|
|
93
|
+
- lib/dialecto_rails/usages/summary.rb
|
|
94
|
+
- lib/dialecto_rails/usages/templates.rb
|
|
95
|
+
- lib/dialecto_rails/usages/wrappers.rb
|
|
96
|
+
- lib/dialecto_rails/version.rb
|
|
97
|
+
homepage: https://dialecto.eu
|
|
98
|
+
licenses:
|
|
99
|
+
- MIT
|
|
100
|
+
metadata:
|
|
101
|
+
rubygems_mfa_required: 'true'
|
|
102
|
+
rdoc_options: []
|
|
103
|
+
require_paths:
|
|
104
|
+
- lib
|
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '3.2'
|
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
|
+
requirements:
|
|
112
|
+
- - ">="
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
version: '0'
|
|
115
|
+
requirements: []
|
|
116
|
+
rubygems_version: 4.0.16
|
|
117
|
+
specification_version: 4
|
|
118
|
+
summary: 'Dialecto for Rails: scan locale files, find call sites, edit text in context.'
|
|
119
|
+
test_files: []
|