Module: BiDiGenerate Private
- Defined in:
- lib/selenium/webdriver/bidi/support/bidi_generate.rb,
lib/selenium/webdriver/bidi/support/check_generated.rb
Overview
This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.
Generates Ruby WebDriver BiDi protocol modules from the shared, binding-neutral BiDi schema produced by the JavaScript generator (see PR #17700):
//javascript/selenium-webdriver:create-bidi-src_schema -> bidi-schema.json
The schema is already normalized (inline enums hoisted, unions canonicalized, group composition flattened, wire names and nullability preserved verbatim), so this generator is a straight projection into Ruby with no CDDL interpretation.
Invoked via bazel run //rb/lib/selenium/webdriver:bidi-generate. Bazel passes
the schema path (resolved through runfiles) plus the workspace-relative output
directory as ARGV. Can also be run directly:
ruby bidi_generate.rb schema.json output/dir
Defined Under Namespace
Classes: Command, Enum, Event, FieldIR, Module, Param, Schema, TypeClass, UnionClass, VariantIR
Constant Summary collapse
- BIDI_DOC_URL =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Companion to the generated
@api privatetags: the page explaining why the BiDi implementation layer is internal and what higher-level API to use instead (see #17628). 'https://www.selenium.dev/documentation/warnings/bidi-implementation/'- LINE_LIMIT =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
RuboCop's Layout/LineLength max; emitted Serialization::Record.define calls wrap to stay within it.
120- RUBY_RESERVED =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Ruby keywords that cannot be used as method names unquoted.
%w[begin end rescue ensure raise return yield if unless while until for do case when then class module def].freeze
- RESERVED_FIELD_NAMES =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Object/Data methods a Data member name would shadow (breaking value semantics or reflection), e.g. a "method" field overriding Object#method.
(RUBY_RESERVED + %w[method hash class send dup clone freeze inspect to_h to_s members with deconstruct deconstruct_keys object_id tap itself then display extensible extensions]).freeze
- PARAMS_CLASS_KINDS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Param kinds the named args can construct a Parameters object for (record fields, or a union dispatched to one of its variants); anything else forwards a raw hash.
%w[record union].freeze
Class Method Summary collapse
- .build_command(schema, cmd) ⇒ Object private
- .build_event(schema, event) ⇒ Object private
- .build_ir(schema) ⇒ Object private
- .call(schema_path, output_dir) ⇒ Object private
- .camel_to_snake(str) ⇒ Object private
-
.check!(schema_rootpath) ⇒ Object
private
Verifies the checked-in protocol .rb match what the generator would produce from the current schema — catching a hand-edit or a forgotten regeneration.
-
.emit(modules, output_dir, template, extension) ⇒ Object
private
Renders every module through one template and writes the result into target, one file per module.
-
.enum_const_path(type_name) ⇒ Object
private
Domain-qualified path to an enum's frozen hash constant ("browsingContext.ReadinessState" → "BrowsingContext::READINESS_STATE"), so a generated command method can reference it for an outbound membership check.
-
.enum_key(value) ⇒ Object
private
snake_case hash key for an enum value (only a label for the wire value it maps to).
-
.nest_synthetic(types) ⇒ Object
private
The projector tags lifted-out types with owner, label.
-
.rbs_nilable(type) ⇒ Object
private
Makes an RBS type admit nil, idempotently (an already-nilable or opaque type is left as-is).
- .render(mod, template_path) ⇒ Object private
-
.ruby_literal(value) ⇒ Object
private
Source literal for a discriminator/const value (string, boolean, or number).
-
.safe_field_name(name) ⇒ Object
private
Append underscore to a field name that would shadow a core method; the wire name is unaffected, only the Ruby reader is renamed.
-
.safe_method_name(name) ⇒ Object
private
Append underscore to avoid clashing with Ruby reserved keywords.
-
.schema_path(rootpath) ⇒ Object
private
$(rootpath) is relative to the runfiles root; dir anchors us there so it resolves the same way locally and on RBE (an execpath would not).
-
.screaming_snake(camel) ⇒ Object
private
SCREAMING_SNAKE constant name for an enum, matching the EVENTS map style (ReadinessState → READINESS_STATE).
-
.sig_dir(output_dir) ⇒ Object
private
The RBS signatures mirror the source tree under sig/ (the repo's convention), e.g.
- .snake_to_class_name(snake) ⇒ Object private
-
.type_class_name(type_name) ⇒ Object
private
Local constant for a domain-scoped type: "script.LocalValue" -> "LocalValue".
-
.type_ruby_path(type_name) ⇒ Object
private
Protocol-relative class path: "script.LocalValue" -> "Script::LocalValue".
-
.wrap_call(prefix, args, indent, open: '(', close: ')') ⇒ Object
private
Renders
prefix(args)on one line, or one argument per line when it would exceed LINE_LIMIT at the given indent.
Class Method Details
.build_command(schema, cmd) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 850 def self.build_command(schema, cmd) params = schema.params_for(cmd['params']) # A param that can't flatten to a typed object (alias or non-record union) would be # silently dropped, so fail generation and handle that shape deliberately if it appears. if cmd['params'] && params.nil? raise "command #{cmd['method']} has params that cannot be expressed as a typed object" end params_ref = cmd['params'] && cmd['params']['ref'] params_kind = schema.type_kind(params_ref) params_class = type_class_name(params_ref) if !params.empty? && PARAMS_CLASS_KINDS.include?(params_kind) Command.new( wire_name: cmd['method'], method_name: safe_method_name(camel_to_snake(cmd['name'])), params: params, result_ref: cmd['result'] && schema.structured_ref(cmd['result']['ref']), params_class: params_class, union_params: params_kind == 'union' ) end |
.build_event(schema, event) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
871 872 873 874 875 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 871 def self.build_event(schema, event) params = event['params'] payload_ref = params && params['ref'] && schema.structured_ref(params['ref']) Event.new(wire_name: event['method'], event_name: camel_to_snake(event['name']), payload_ref: payload_ref) end |
.build_ir(schema) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
836 837 838 839 840 841 842 843 844 845 846 847 848 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 836 def self.build_ir(schema) schema.domains.map do |domain| Module.new( name: domain, ruby_class: snake_to_class_name(camel_to_snake(domain)), filename: camel_to_snake(domain), commands: schema.commands_for(domain).map { |cmd| build_command(schema, cmd) }, events: schema.events_for(domain).map { |ev| build_event(schema, ev) }, enums: schema.enums_for(domain), types: nest_synthetic(schema.types_for(domain)) ) end end |
.call(schema_path, output_dir) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
897 898 899 900 901 902 903 904 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 897 def self.call(schema_path, output_dir) raw = load_json(schema_path) schema = Schema.new(raw) modules = build_ir(schema) emit(modules, output_dir, 'module.rb.erb', 'rb') emit(modules, sig_dir(output_dir), 'module.rbs.erb', 'rbs') end |
.camel_to_snake(str) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
50 51 52 53 54 55 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 50 def self.camel_to_snake(str) str .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') .gsub(/([a-z\d])([A-Z])/, '\1_\2') .downcase end |
.check!(schema_rootpath) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Verifies the checked-in protocol .rb match what the generator would produce from the current schema — catching a hand-edit or a forgotten regeneration. Re-renders each module in memory (no file writes) and compares. The .rbs are covered by Steep.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/selenium/webdriver/bidi/support/check_generated.rb', line 27 def self.check!(schema_rootpath) modules = build_ir(Schema.new(JSON.parse(File.read(schema_path(schema_rootpath))))) protocol_dir = File.('../protocol', __dir__) template = File.join(__dir__, 'templates', 'module.rb.erb') stale = modules.reject do |mod| path = File.join(protocol_dir, "#{mod.filename}.rb") File.exist?(path) && File.read(path) == render(mod, template) end return if stale.empty? warn "Generated BiDi protocol code is stale or hand-edited: #{stale.map { |m| "#{m.filename}.rb" }.sort.join(', ')}" warn 'Regenerate with: bazel run //rb/lib/selenium/webdriver:bidi-generate' exit 1 end |
.emit(modules, output_dir, template, extension) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Renders every module through one template and writes the result into target, one file per module. Used for both the Ruby source and its RBS signatures.
908 909 910 911 912 913 914 915 916 917 918 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 908 def self.emit(modules, output_dir, template, extension) target = File.join(workspace_root, output_dir) FileUtils.mkdir_p(target) tmpl = File.join(File.dirname(__FILE__), 'templates', template) modules.each do |mod| path = File.join(target, "#{mod.filename}.#{extension}") File.write(path, render(mod, tmpl)) warn "bidi-generate: wrote #{path}" end end |
.enum_const_path(type_name) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Domain-qualified path to an enum's frozen hash constant ("browsingContext.ReadinessState" → "BrowsingContext::READINESS_STATE"), so a generated command method can reference it for an outbound membership check.
128 129 130 131 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 128 def self.enum_const_path(type_name) domain, local = type_name.split('.', 2) "#{snake_to_class_name(camel_to_snake(domain))}::#{screaming_snake(local)}" end |
.enum_key(value) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
snake_case hash key for an enum value (only a label for the wire value it maps to). Preserves camelCase word boundaries (beforeRequestSent → before_request_sent), maps a leading minus to "neg" (-0 → neg0, -Infinity → neg_infinity; no underscore before a digit, so the key stays normalcase), and collapses other punctuation (dedicated-worker → dedicated_worker).
138 139 140 141 142 143 144 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 138 def self.enum_key(value) camel_to_snake(value.to_s) .sub(/\A-(?=\d)/, 'neg') .sub(/\A-/, 'neg_') .gsub(/[^a-z0-9]+/, '_') .gsub(/\A_+|_+\z/, '') end |
.nest_synthetic(types) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
The projector tags lifted-out types with owner, label. Emit each
synthetic record inside its owner's class body under its bare label, so
Owner_Label becomes the nested Owner::Label (refs resolve there via
ruby_path). Synthetic enums stay domain-level. Raises on a missing owner.
881 882 883 884 885 886 887 888 889 890 891 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 881 def self.nest_synthetic(types) index = types.to_h { |t| [t.schema_name, t] } children = types.select { |t| !t.union? && t.synthetic } children.each do |child| owner = index[child.owner] || raise("synthetic type #{child.schema_name} has no emitted owner #{child.owner}") owner.nested = (owner.nested || []) << child child.ruby_name = child.label end types - children end |
.rbs_nilable(type) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Makes an RBS type admit nil, idempotently (an already-nilable or opaque type is left
as-is). Applied to a field whose schema type is nullable, so its value type allows nil;
keyword-optionality is expressed separately by the ? prefix (see rbs_part / rbs_arg).
119 120 121 122 123 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 119 def self.rbs_nilable(type) return type if type == 'untyped' || type == 'nil' || type.end_with?('?') "#{type}?" end |
.render(mod, template_path) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
893 894 895 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 893 def self.render(mod, template_path) ERB.new(File.read(template_path), trim_mode: '-').result(binding) end |
.ruby_literal(value) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Source literal for a discriminator/const value (string, boolean, or number).
75 76 77 78 79 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 75 def self.ruby_literal(value) return 'nil' if value.nil? value.is_a?(String) ? "'#{value}'" : value.to_s end |
.safe_field_name(name) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Append underscore to a field name that would shadow a core method; the wire name is unaffected, only the Ruby reader is renamed.
106 107 108 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 106 def self.safe_field_name(name) RESERVED_FIELD_NAMES.include?(name) ? "#{name}_" : name end |
.safe_method_name(name) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Append underscore to avoid clashing with Ruby reserved keywords.
93 94 95 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 93 def self.safe_method_name(name) RUBY_RESERVED.include?(name) ? "#{name}_" : name end |
.schema_path(rootpath) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
$(rootpath) is relative to the runfiles root; dir anchors us there so it resolves the
same way locally and on RBE (an execpath would not). This file lives at
rb/lib/selenium/webdriver/bidi/support, so expand six levels up to the root — File.expand_path
is separator-agnostic, unlike stripping a "/"-spelled suffix (which would miss on Windows).
The cwd-relative rootpath fallback matches how spec_support's rlocation resolves.
48 49 50 51 52 |
# File 'lib/selenium/webdriver/bidi/support/check_generated.rb', line 48 def self.schema_path(rootpath) runfiles_root = File.('../../../../../..', __dir__) [File.join(runfiles_root, rootpath), rootpath].find { |p| File.exist?(p) } || raise("BiDi schema not found (looked for #{rootpath})") end |
.screaming_snake(camel) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
SCREAMING_SNAKE constant name for an enum, matching the EVENTS map style (ReadinessState → READINESS_STATE).
112 113 114 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 112 def self.screaming_snake(camel) camel_to_snake(camel).upcase end |
.sig_dir(output_dir) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
The RBS signatures mirror the source tree under sig/ (the repo's convention), e.g. rb/lib/.../protocol -> rb/sig/lib/.../protocol.
922 923 924 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 922 def self.sig_dir(output_dir) output_dir.sub(%r{(\A|/)lib/}, '\1sig/lib/') end |
.snake_to_class_name(snake) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
57 58 59 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 57 def self.snake_to_class_name(snake) snake.split('_').map(&:capitalize).join end |
.type_class_name(type_name) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Local constant for a domain-scoped type: "script.LocalValue" -> "LocalValue". The first letter is capitalized so a lower-cased spec name (e.g. "permissions.setPermission") still yields a valid Ruby constant.
64 65 66 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 64 def self.type_class_name(type_name) type_name.split('.', 2).last.sub(/\A[a-z]/, &:upcase) end |
.type_ruby_path(type_name) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Protocol-relative class path: "script.LocalValue" -> "Script::LocalValue".
69 70 71 72 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 69 def self.type_ruby_path(type_name) domain = type_name.split('.', 2).first "#{snake_to_class_name(camel_to_snake(domain))}::#{type_class_name(type_name)}" end |
.wrap_call(prefix, args, indent, open: '(', close: ')') ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Renders prefix(args) on one line, or one argument per line when it would exceed
LINE_LIMIT at the given indent. open/close default to parentheses (pass {} for a
hash literal) so emitted calls and literals stay within RuboCop's length limit.
84 85 86 87 88 89 90 |
# File 'lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 84 def self.wrap_call(prefix, args, indent, open: '(', close: ')') one_line = "#{prefix}#{open}#{args.join(', ')}#{close}" return one_line if args.empty? || indent + one_line.length <= LINE_LIMIT pad = ' ' * (indent + 2) "#{prefix}#{open}\n#{pad}#{args.join(",\n#{pad}")}\n#{' ' * indent}#{close}" end |