pug-sh 0.0.1

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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +247 -0
  4. data/lib/pug/batcher.rb +384 -0
  5. data/lib/pug/client.rb +248 -0
  6. data/lib/pug/connect.rb +291 -0
  7. data/lib/pug/countries.rb +19 -0
  8. data/lib/pug/errors.rb +16 -0
  9. data/lib/pug/event_builder.rb +522 -0
  10. data/lib/pug/gen/buf/validate/validate_pb.rb +59 -0
  11. data/lib/pug/gen/common/events/v1/api_events_pb.rb +26 -0
  12. data/lib/pug/gen/common/events/v1/app_events_pb.rb +33 -0
  13. data/lib/pug/gen/common/events/v1/auth_events_pb.rb +32 -0
  14. data/lib/pug/gen/common/events/v1/billing_events_pb.rb +41 -0
  15. data/lib/pug/gen/common/events/v1/chat_events_pb.rb +56 -0
  16. data/lib/pug/gen/common/events/v1/commerce_events_pb.rb +38 -0
  17. data/lib/pug/gen/common/events/v1/discovery_events_pb.rb +31 -0
  18. data/lib/pug/gen/common/events/v1/error_events_pb.rb +26 -0
  19. data/lib/pug/gen/common/events/v1/file_events_pb.rb +29 -0
  20. data/lib/pug/gen/common/events/v1/form_events_pb.rb +27 -0
  21. data/lib/pug/gen/common/events/v1/integration_events_pb.rb +27 -0
  22. data/lib/pug/gen/common/events/v1/invitation_events_pb.rb +26 -0
  23. data/lib/pug/gen/common/events/v1/media_events_pb.rb +36 -0
  24. data/lib/pug/gen/common/events/v1/navigation_events_pb.rb +31 -0
  25. data/lib/pug/gen/common/events/v1/notification_events_pb.rb +28 -0
  26. data/lib/pug/gen/common/events/v1/options_pb.rb +25 -0
  27. data/lib/pug/gen/common/events/v1/social_events_pb.rb +25 -0
  28. data/lib/pug/gen/common/events/v1/support_events_pb.rb +33 -0
  29. data/lib/pug/gen/common/events/v1/workspace_events_pb.rb +30 -0
  30. data/lib/pug/gen/common/v1/filter_schema_pb.rb +29 -0
  31. data/lib/pug/gen/common/v1/filters_pb.rb +27 -0
  32. data/lib/pug/gen/common/v1/property_value_pb.rb +24 -0
  33. data/lib/pug/gen/common/v1/time_pb.rb +25 -0
  34. data/lib/pug/gen/sdk/devices/v1/devices_pb.rb +35 -0
  35. data/lib/pug/gen/sdk/events/v1/events_pb.rb +30 -0
  36. data/lib/pug/gen/sdk/profiles/v1/profiles_pb.rb +28 -0
  37. data/lib/pug/gen/shared/activity/v1/activity_pb.rb +46 -0
  38. data/lib/pug/gen/shared/insights/v1/insights_pb.rb +69 -0
  39. data/lib/pug/gen/shared/profiles/v1/profiles_pb.rb +43 -0
  40. data/lib/pug/log.rb +35 -0
  41. data/lib/pug/proto.rb +22 -0
  42. data/lib/pug/reads.rb +118 -0
  43. data/lib/pug/rpc.rb +73 -0
  44. data/lib/pug/validator.rb +475 -0
  45. data/lib/pug/version.rb +5 -0
  46. data/lib/pug/well_known.rb +33 -0
  47. data/lib/pug-sh.rb +3 -0
  48. data/lib/pug.rb +5 -0
  49. metadata +122 -0
@@ -0,0 +1,475 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+ require_relative "proto"
5
+
6
+ module Pug
7
+ # The subset of protovalidate the SDK's schemas use, driven by the buf.validate rules in the
8
+ # descriptors' options, since Ruby has no protovalidate. A rule it can't evaluate is skipped
9
+ # and reported by unsupported_rules, which the tests require to be empty for every schema the
10
+ # SDK validates: a proto sync that adopts a new rule fails CI instead of going unenforced.
11
+ #
12
+ # Each message's rules are compiled once into plain closures, since reading them back off the
13
+ # protobuf rule messages on every call costs more than the checks themselves.
14
+ module Validator
15
+ Violation = Struct.new(:path, :rule_id, :message) do
16
+ def initialize(path, rule_id, message)
17
+ super(path.to_s, rule_id, message)
18
+ end
19
+
20
+ def to_s
21
+ path.empty? ? "#{message} [#{rule_id}]" : "#{path}: #{message} [#{rule_id}]"
22
+ end
23
+ end
24
+
25
+ FieldPlan = Struct.new(:field, :name, :presence, :required, :ignore, :shape, :subtype, :nested, :convert,
26
+ :checks, :unsupported)
27
+ MessagePlan = Struct.new(:name, :fields, :oneofs, :checks, :unsupported, :active, :checked_fields)
28
+
29
+ # A map entry's path, rendered only if a violation needs it.
30
+ MapEntryPath = Struct.new(:parent, :name, :key) do
31
+ def empty? = false
32
+
33
+ def to_s
34
+ "#{Validator.join(parent, name)}[#{key.inspect}]"
35
+ end
36
+ end
37
+
38
+ MESSAGE_RULES = Proto.extension("buf.validate.message")
39
+ ONEOF_RULES = Proto.extension("buf.validate.oneof")
40
+ FIELD_RULES = Proto.extension("buf.validate.field")
41
+
42
+ NUMERIC = %i[float double int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64].freeze
43
+ RANGE = %w[const lt lte gt gte in not_in example].freeze
44
+ SUPPORTED = {
45
+ string: %w[const len min_len max_len len_bytes min_bytes max_bytes pattern prefix suffix contains
46
+ not_contains in not_in uuid tuuid example],
47
+ bool: %w[const example],
48
+ duration: RANGE,
49
+ **NUMERIC.to_h { |type| [type, %i[float double].include?(type) ? [*RANGE, "finite"] : RANGE] }
50
+ }.freeze
51
+
52
+ UUID = /\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/
53
+ TUUID = /\A\h{32}\z/
54
+ NANOS = 1_000_000_000
55
+ BOUNDS = { lt: "less than", lte: "less than or equal to", gt: "greater than",
56
+ gte: "greater than or equal to" }.freeze
57
+
58
+ # Ruby has no CEL interpreter, so each expression the schemas use is implemented by hand and
59
+ # keyed by its exact text: an expression changed upstream stops matching and shows up in
60
+ # unsupported_rules rather than being enforced with stale semantics.
61
+ CEL = {
62
+ "this.auto_properties.all(key, key.startsWith('$'))" =>
63
+ ->(event) { event.auto_properties.keys.all? { |key| key.start_with?("$") } },
64
+ "this.custom_properties.all(key, !key.startsWith('$'))" =>
65
+ ->(event) { event.custom_properties.keys.none? { |key| key.start_with?("$") } },
66
+ "this.cookieless || (this.distinct_id != '' && this.session_id != '')" =>
67
+ ->(event) { event.cookieless || (event.distinct_id != "" && event.session_id != "") },
68
+ "!this.startsWith('cookieless-')" =>
69
+ ->(value) { !value.start_with?("cookieless-") }
70
+ }.freeze
71
+
72
+ @plans = {}.freeze
73
+ @lock = Mutex.new
74
+
75
+ module_function
76
+
77
+ def validate(message)
78
+ violations = []
79
+ check_message(message, plan(message.class.descriptor), "", violations)
80
+ violations
81
+ end
82
+
83
+ def format(violations)
84
+ violations.join(", ")
85
+ end
86
+
87
+ def unsupported_rules(descriptor)
88
+ seen = {}
89
+ pending = [plan(descriptor)]
90
+ found = []
91
+ while (current = pending.pop)
92
+ next if seen[current.name]
93
+
94
+ seen[current.name] = true
95
+ found.concat(current.unsupported)
96
+ current.fields.each do |field_plan|
97
+ found.concat(field_plan.unsupported)
98
+ pending << plan(field_plan.subtype) if field_plan.subtype
99
+ end
100
+ end
101
+ found
102
+ end
103
+
104
+ # Plans are built under the lock and published copy-on-write, so readers never lock.
105
+ def plan(descriptor)
106
+ @plans[descriptor.name] || @lock.synchronize { build_plans(descriptor) }
107
+ end
108
+
109
+ def check_message(message, message_plan, path, out)
110
+ return unless message_plan.active
111
+
112
+ message_plan.checked_fields.each { |field_plan| check_field(message, field_plan, path, out) }
113
+ message_plan.oneofs.each do |name|
114
+ next unless message.public_send(name).nil?
115
+
116
+ out << Violation.new(join(path, name), "required", "exactly one field is required in oneof")
117
+ end
118
+ message_plan.checks.each do |check|
119
+ failure = check.call(message)
120
+ out << Violation.new(path, *failure) if failure
121
+ end
122
+ end
123
+
124
+ def check_field(message, field_plan, path, out)
125
+ field = field_plan.field
126
+ present = case field_plan.presence
127
+ when :explicit then field.has?(message)
128
+ when :repeated then field.get(message).length.positive?
129
+ else field.get(message) != field.default
130
+ end
131
+ if field_plan.required && !present
132
+ out << Violation.new(join(path, field_plan.name), "required", "value is required")
133
+ return
134
+ end
135
+ return unless present || (field_plan.presence == :implicit && field_plan.ignore != :IGNORE_IF_ZERO_VALUE)
136
+
137
+ value = field.get(message)
138
+ nested = field_plan.nested && plan(field_plan.nested)
139
+ # A list or map is planned only for required, then, which its presence has satisfied.
140
+ return if nested.nil? && field_plan.shape != :single
141
+
142
+ case field_plan.shape
143
+ when :map
144
+ value.each { |key, item| check_message(item, nested, MapEntryPath.new(path, field_plan.name, key), out) }
145
+ when :list
146
+ value.each_with_index { |item, i| check_message(item, nested, "#{join(path, field_plan.name)}[#{i}]", out) }
147
+ else
148
+ run_checks(field_plan, value, path, out) unless field_plan.checks.empty?
149
+ check_message(value, nested, join(path, field_plan.name), out) if nested
150
+ end
151
+ end
152
+
153
+ def run_checks(field_plan, value, path, out)
154
+ value = field_plan.convert.call(value) if field_plan.convert
155
+ field_plan.checks.each do |check|
156
+ failure = check.call(value)
157
+ out << Violation.new(join(path, field_plan.name), *failure) if failure
158
+ end
159
+ end
160
+
161
+ def set?(message, field)
162
+ if field.label == :repeated
163
+ field.get(message).length.positive?
164
+ elsif field.has_presence?
165
+ field.has?(message)
166
+ else
167
+ field.get(message) != field.default
168
+ end
169
+ end
170
+
171
+ def join(path, name)
172
+ path.empty? ? name : "#{path}.#{name}"
173
+ end
174
+
175
+ def build_plans(root)
176
+ return @plans[root.name] if @plans.key?(root.name)
177
+
178
+ built = {}
179
+ pending = [root]
180
+ while (descriptor = pending.pop)
181
+ next if @plans.key?(descriptor.name) || built.key?(descriptor.name)
182
+
183
+ built[descriptor.name] = build_plan(descriptor)
184
+ built[descriptor.name].fields.each { |field_plan| pending << field_plan.subtype if field_plan.subtype }
185
+ end
186
+ mark_active(built)
187
+ @plans = @plans.merge(built.transform_values(&:freeze)).freeze
188
+ @plans[root.name]
189
+ end
190
+
191
+ def build_plan(descriptor)
192
+ rules = MESSAGE_RULES.get(descriptor.options)
193
+ cel = rules&.cel.to_a
194
+ unsupported = cel.reject { |rule| CEL.key?(rule.expression) }.map { |rule| "#{descriptor.name}: cel #{rule.id}" }
195
+ unsupported << "#{descriptor.name}: message oneof rule" if rules&.oneof&.any?
196
+ unsupported << "#{descriptor.name}: cel_expression" if rules&.cel_expression&.any?
197
+ oneofs = []
198
+ descriptor.each_oneof do |oneof|
199
+ oneofs << oneof.name if ONEOF_RULES.get(oneof.options)&.required
200
+ end
201
+ fields = descriptor.map { |field| build_field_plan(descriptor, field) }
202
+ MessagePlan.new(descriptor.name, fields.freeze, oneofs.freeze, cel.filter_map { |rule| cel_check(rule) }.freeze,
203
+ unsupported.freeze, false, nil)
204
+ end
205
+
206
+ def build_field_plan(descriptor, field)
207
+ rules = FIELD_RULES.get(field.options)
208
+ shape = if field.label != :repeated then :single
209
+ elsif field.type == :message && field.subtype.options.map_entry then :map
210
+ else :list
211
+ end
212
+ subtype = if shape == :map
213
+ value = field.subtype.lookup("value")
214
+ value.subtype if value.type == :message
215
+ elsif field.type == :message
216
+ field.subtype
217
+ end
218
+ unsupported = unsupported_field_rules(rules, shape).map { |rule| "#{descriptor.name}.#{field.name}: #{rule}" }
219
+ checks = shape == :single && rules ? field_checks(rules) : []
220
+ convert = method(:seconds) if rules&.type == :duration
221
+ presence = if field.label == :repeated then :repeated
222
+ elsif field.has_presence? then :explicit
223
+ else :implicit
224
+ end
225
+ FieldPlan.new(field, field.name, presence, rules&.required || false, rules&.ignore, shape, subtype, nil, convert,
226
+ checks.freeze, unsupported.freeze)
227
+ end
228
+
229
+ def unsupported_field_rules(rules, shape)
230
+ return [] if rules.nil?
231
+
232
+ found = rules.cel.reject { |rule| shape == :single && CEL.key?(rule.expression) }.map { |rule| "cel #{rule.id}" }
233
+ found << "cel_expression" if rules.cel_expression.any?
234
+ type = rules.type
235
+ return found if type.nil?
236
+ return found << type.to_s if !SUPPORTED.key?(type) || shape != :single
237
+
238
+ typed = rules.public_send(type)
239
+ typed.class.descriptor.each do |rule|
240
+ found << "#{type}.#{rule.name}" if !SUPPORTED[type].include?(rule.name) && set?(typed, rule)
241
+ end
242
+ found
243
+ end
244
+
245
+ # A message is worth walking only if some rule is reachable from it; recursive schemas such
246
+ # as google.protobuf.Struct, used for identify traits, have none and are skipped whole.
247
+ def mark_active(built)
248
+ lookup = ->(name) { built[name] || @plans[name] }
249
+ reaches_rule = ->(field_plan) { field_plan.subtype && lookup.call(field_plan.subtype.name).active }
250
+ built.each_value do |message_plan|
251
+ message_plan.active = message_plan.checks.any? || message_plan.oneofs.any? ||
252
+ message_plan.fields.any? { |field_plan| field_plan.required || field_plan.checks.any? }
253
+ end
254
+ loop do
255
+ reached = built.each_value.reject(&:active).select { |message_plan| message_plan.fields.any?(&reaches_rule) }
256
+ break if reached.empty?
257
+
258
+ reached.each { |message_plan| message_plan.active = true }
259
+ end
260
+ # Only fields with a rule, or leading to one, are walked; nested messages without any are
261
+ # dropped from the plan.
262
+ built.each_value do |message_plan|
263
+ message_plan.checked_fields = message_plan.fields.filter_map do |field_plan|
264
+ next if field_plan.ignore == :IGNORE_ALWAYS
265
+
266
+ field_plan.nested = field_plan.subtype if reaches_rule.call(field_plan)
267
+ field_plan if field_plan.nested || field_plan.required || field_plan.checks.any?
268
+ end.freeze
269
+ end
270
+ end
271
+
272
+ # Checks take the value and return nil, or the failed rule's [id, message].
273
+ def field_checks(rules)
274
+ checks = rules.cel.filter_map { |rule| cel_check(rule) }
275
+ type = rules.type
276
+ SUPPORTED.key?(type) ? checks + type_checks(type, rules.public_send(type)) : checks
277
+ end
278
+
279
+ def cel_check(rule)
280
+ check = CEL[rule.expression]
281
+ failure = [rule.id, rule.message].freeze
282
+ check && ->(value) { failure unless check.call(value) }
283
+ end
284
+
285
+ def type_checks(type, rules)
286
+ case type
287
+ when :string then string_checks(rules)
288
+ when :bool then const_checks(type, rules)
289
+ else
290
+ checks = const_checks(type, rules) + bounds_checks(type, rules) + list_checks(type, rules)
291
+ if rules.respond_to?(:finite) && rules.finite
292
+ failure = ["#{type}.finite", "value must be finite"].freeze
293
+ checks << ->(value) { failure unless value.finite? }
294
+ end
295
+ checks
296
+ end
297
+ end
298
+
299
+ def string_checks(rules)
300
+ checks = const_checks(:string, rules)
301
+ rule = lambda do |id, message, &failed|
302
+ failure = ["string.#{id}", message].freeze
303
+ checks << ->(value) { failure if failed.call(value) }
304
+ end
305
+ { len: "", min_len: "at least ", max_len: "at most " }.each do |name, bound|
306
+ next unless rules.public_send(:"has_#{name}?")
307
+
308
+ limit = rules.public_send(name)
309
+ rule.call(name, "value length must be #{bound}#{limit} characters") do |value|
310
+ length_violated?(name, value.length, limit)
311
+ end
312
+ end
313
+ { len_bytes: "", min_bytes: "at least ", max_bytes: "at most " }.each do |name, bound|
314
+ next unless rules.public_send(:"has_#{name}?")
315
+
316
+ limit = rules.public_send(name)
317
+ rule.call(name, "value length must be #{bound}#{limit} bytes") do |value|
318
+ length_violated?(name, value.bytesize, limit)
319
+ end
320
+ end
321
+ if rules.has_pattern?
322
+ pattern = Regexp.new(re2_anchors(rules.pattern))
323
+ rule.call(:pattern, "value does not match regex pattern `#{rules.pattern}`") { |value| !pattern.match?(value) }
324
+ end
325
+ if rules.has_prefix?
326
+ prefix = rules.prefix
327
+ rule.call(:prefix, "value does not have prefix `#{prefix}`") { |value| !value.start_with?(prefix) }
328
+ end
329
+ if rules.has_suffix?
330
+ suffix = rules.suffix
331
+ rule.call(:suffix, "value does not have suffix `#{suffix}`") { |value| !value.end_with?(suffix) }
332
+ end
333
+ if rules.has_contains?
334
+ needle = rules.contains
335
+ rule.call(:contains, "value does not contain substring `#{needle}`") { |value| !value.include?(needle) }
336
+ end
337
+ if rules.has_not_contains?
338
+ excluded = rules.not_contains
339
+ rule.call(:not_contains, "value contains substring `#{excluded}`") { |value| value.include?(excluded) }
340
+ end
341
+ checks.concat(list_checks(:string, rules))
342
+ checks.concat(uuid_checks(rules))
343
+ end
344
+
345
+ def length_violated?(name, length, limit)
346
+ if name.start_with?("min") then length < limit
347
+ elsif name.start_with?("max") then length > limit
348
+ else length != limit
349
+ end
350
+ end
351
+
352
+ def uuid_checks(rules)
353
+ shape, name, id = if rules.uuid then [UUID, "UUID", "uuid"]
354
+ elsif rules.tuuid then [TUUID, "trimmed UUID", "tuuid"]
355
+ end
356
+ return [] unless shape
357
+
358
+ empty = ["string.#{id}_empty", "value is empty, which is not a valid #{name}"].freeze
359
+ invalid = ["string.#{id}", "value must be a valid #{name}"].freeze
360
+ check = lambda do |value|
361
+ if value.empty? then empty
362
+ elsif !shape.match?(value) then invalid
363
+ end
364
+ end
365
+ [check]
366
+ end
367
+
368
+ def const_checks(type, rules)
369
+ return [] unless rules.has_const?
370
+
371
+ expected = comparable(type, rules.const)
372
+ shown = type == :string ? "`#{rules.const}`" : show(type, rules.const)
373
+ failure = ["#{type}.const", "value must equal #{shown}"].freeze
374
+ [->(value) { failure unless value == expected }]
375
+ end
376
+
377
+ # protovalidate's lt/lte/gt/gte family in one place. With both bounds, a lower bound above
378
+ # the upper one means "outside the gap" (the *_exclusive rules). NaN fails every comparison,
379
+ # which is what the double rules' explicit isNan() checks amount to.
380
+ def bounds_checks(type, rules)
381
+ lower_op = rules.greater_than
382
+ upper_op = rules.less_than
383
+ return [] if lower_op.nil? && upper_op.nil?
384
+
385
+ lower = lower_op && rules.public_send(lower_op)
386
+ upper = upper_op && rules.public_send(upper_op)
387
+ lower_bound = lower && comparable(type, lower)
388
+ upper_bound = upper && comparable(type, upper)
389
+ if lower && upper
390
+ exclusive = upper_bound < lower_bound
391
+ failure = ["#{type}.#{lower_op}_#{upper_op}#{"_exclusive" if exclusive}",
392
+ "value must be #{BOUNDS[lower_op]} #{show(type, lower)} #{exclusive ? "or" : "and"} " \
393
+ "#{BOUNDS[upper_op]} #{show(type, upper)}"].freeze
394
+ [lambda do |value|
395
+ lower_ok = satisfies?(value, lower_op, lower_bound)
396
+ upper_ok = satisfies?(value, upper_op, upper_bound)
397
+ failure unless exclusive ? lower_ok || upper_ok : lower_ok && upper_ok
398
+ end]
399
+ else
400
+ op = lower_op || upper_op
401
+ bound = lower_bound || upper_bound
402
+ failure = ["#{type}.#{op}", "value must be #{BOUNDS[op]} #{show(type, lower || upper)}"].freeze
403
+ [->(value) { failure unless satisfies?(value, op, bound) }]
404
+ end
405
+ end
406
+
407
+ def list_checks(type, rules)
408
+ checks = []
409
+ unless rules.in.empty?
410
+ allowed = rules.in.map { |item| comparable(type, item) }
411
+ missing = ["#{type}.in", "value must be in list #{list(rules.in, type)}"].freeze
412
+ checks << ->(value) { missing unless allowed.include?(value) }
413
+ end
414
+ unless rules.not_in.empty?
415
+ denied = rules.not_in.map { |item| comparable(type, item) }
416
+ forbidden = ["#{type}.not_in", "value must not be in list #{list(rules.not_in, type)}"].freeze
417
+ checks << ->(value) { forbidden if denied.include?(value) }
418
+ end
419
+ checks
420
+ end
421
+
422
+ def satisfies?(value, operator, bound)
423
+ case operator
424
+ when :lt then value < bound
425
+ when :lte then value <= bound
426
+ when :gt then value > bound
427
+ else value >= bound
428
+ end
429
+ end
430
+
431
+ def comparable(type, value)
432
+ type == :duration ? seconds(value) : value
433
+ end
434
+
435
+ def seconds(message)
436
+ Rational((message.seconds * NANOS) + message.nanos, NANOS)
437
+ end
438
+
439
+ def show(type, value)
440
+ case type
441
+ when :duration
442
+ total = seconds(value)
443
+ "#{total.denominator == 1 ? total.to_i : total.to_f}s"
444
+ when :float, :double then value.finite? && value == value.truncate ? value.to_i.to_s : value.to_s
445
+ else value.to_s
446
+ end
447
+ end
448
+
449
+ def list(values, type)
450
+ "[#{values.map { |value| show(type, value) }.join(", ")}]"
451
+ end
452
+
453
+ # RE2 anchors ^ and $ at the ends of the text, Ruby at the ends of every line: left as is,
454
+ # "USD\nEUR" would pass ^[A-Z]{3}$ here and fail on the server.
455
+ def re2_anchors(pattern)
456
+ source = +""
457
+ escaped = in_class = false
458
+ pattern.each_char do |char|
459
+ if escaped
460
+ escaped = false
461
+ elsif char == "\\"
462
+ escaped = true
463
+ elsif char == "["
464
+ in_class = true
465
+ elsif char == "]"
466
+ in_class = false
467
+ elsif !in_class && ["^", "$"].include?(char)
468
+ char = char == "^" ? "\\A" : "\\z"
469
+ end
470
+ source << char
471
+ end
472
+ source
473
+ end
474
+ end
475
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pug
4
+ VERSION = "0.0.1"
5
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "proto"
4
+
5
+ module Pug
6
+ # Like sdk-node's generated registry, the catalog holds the events whose
7
+ # (common.events.v1.platforms) option lists PLATFORM_SERVER or is unset; one tagged only for
8
+ # web, mobile or desktop is a custom event here.
9
+ module WellKnown
10
+ KIND = Proto.extension("common.events.v1.kind")
11
+ PLATFORMS = Proto.extension("common.events.v1.platforms")
12
+
13
+ def self.build
14
+ namespace = Proto::Common::Events::V1
15
+ classes = namespace.constants.map { |name| namespace.const_get(name) }
16
+ classes = classes.select { |klass| klass.is_a?(Class) && klass.respond_to?(:descriptor) }
17
+ classes.sort_by { |klass| klass.descriptor.name }.each_with_object({}) do |klass, catalog|
18
+ options = klass.descriptor.options
19
+ kind = KIND.get(options)
20
+ next if kind.empty? || catalog.key?(kind)
21
+
22
+ platforms = PLATFORMS.get(options)
23
+ catalog[kind] = klass if platforms.empty? || platforms.include?(:PLATFORM_SERVER)
24
+ end.freeze
25
+ end
26
+
27
+ CATALOG = build
28
+
29
+ def self.[](kind)
30
+ CATALOG[kind]
31
+ end
32
+ end
33
+ end
data/lib/pug-sh.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pug"
data/lib/pug.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pug/version"
4
+ require_relative "pug/errors"
5
+ require_relative "pug/client"
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pug-sh
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - pug.sh
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: google-protobuf
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '4.30'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '5'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '4.30'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '5'
32
+ - !ruby/object:Gem::Dependency
33
+ name: logger
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '1.6'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '1.6'
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - LICENSE
51
+ - README.md
52
+ - lib/pug-sh.rb
53
+ - lib/pug.rb
54
+ - lib/pug/batcher.rb
55
+ - lib/pug/client.rb
56
+ - lib/pug/connect.rb
57
+ - lib/pug/countries.rb
58
+ - lib/pug/errors.rb
59
+ - lib/pug/event_builder.rb
60
+ - lib/pug/gen/buf/validate/validate_pb.rb
61
+ - lib/pug/gen/common/events/v1/api_events_pb.rb
62
+ - lib/pug/gen/common/events/v1/app_events_pb.rb
63
+ - lib/pug/gen/common/events/v1/auth_events_pb.rb
64
+ - lib/pug/gen/common/events/v1/billing_events_pb.rb
65
+ - lib/pug/gen/common/events/v1/chat_events_pb.rb
66
+ - lib/pug/gen/common/events/v1/commerce_events_pb.rb
67
+ - lib/pug/gen/common/events/v1/discovery_events_pb.rb
68
+ - lib/pug/gen/common/events/v1/error_events_pb.rb
69
+ - lib/pug/gen/common/events/v1/file_events_pb.rb
70
+ - lib/pug/gen/common/events/v1/form_events_pb.rb
71
+ - lib/pug/gen/common/events/v1/integration_events_pb.rb
72
+ - lib/pug/gen/common/events/v1/invitation_events_pb.rb
73
+ - lib/pug/gen/common/events/v1/media_events_pb.rb
74
+ - lib/pug/gen/common/events/v1/navigation_events_pb.rb
75
+ - lib/pug/gen/common/events/v1/notification_events_pb.rb
76
+ - lib/pug/gen/common/events/v1/options_pb.rb
77
+ - lib/pug/gen/common/events/v1/social_events_pb.rb
78
+ - lib/pug/gen/common/events/v1/support_events_pb.rb
79
+ - lib/pug/gen/common/events/v1/workspace_events_pb.rb
80
+ - lib/pug/gen/common/v1/filter_schema_pb.rb
81
+ - lib/pug/gen/common/v1/filters_pb.rb
82
+ - lib/pug/gen/common/v1/property_value_pb.rb
83
+ - lib/pug/gen/common/v1/time_pb.rb
84
+ - lib/pug/gen/sdk/devices/v1/devices_pb.rb
85
+ - lib/pug/gen/sdk/events/v1/events_pb.rb
86
+ - lib/pug/gen/sdk/profiles/v1/profiles_pb.rb
87
+ - lib/pug/gen/shared/activity/v1/activity_pb.rb
88
+ - lib/pug/gen/shared/insights/v1/insights_pb.rb
89
+ - lib/pug/gen/shared/profiles/v1/profiles_pb.rb
90
+ - lib/pug/log.rb
91
+ - lib/pug/proto.rb
92
+ - lib/pug/reads.rb
93
+ - lib/pug/rpc.rb
94
+ - lib/pug/validator.rb
95
+ - lib/pug/version.rb
96
+ - lib/pug/well_known.rb
97
+ homepage: https://pug.sh
98
+ licenses:
99
+ - MIT
100
+ metadata:
101
+ homepage_uri: https://pug.sh
102
+ source_code_uri: https://github.com/pug-sh/sdk-ruby
103
+ bug_tracker_uri: https://github.com/pug-sh/sdk-ruby/issues
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '3.3'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubygems_version: 4.0.21
119
+ specification_version: 4
120
+ summary: Pug server-side SDK for Ruby — event tracking, profile identify, and analytics
121
+ reads.
122
+ test_files: []