diff --git a/Makefile b/Makefile index 1cc718a..c0cf532 100644 --- a/Makefile +++ b/Makefile @@ -124,7 +124,6 @@ build-launcher: check-arch package-linux: python3 scripts/package.py linux \ --includes \ - settings/omegacfg.jvv \ settings/properties.json \ bundle/fontconfigs \ --version $(version) \ diff --git a/jsonvv/README.md b/jsonvv/README.md deleted file mode 100644 index b11a14c..0000000 --- a/jsonvv/README.md +++ /dev/null @@ -1,728 +0,0 @@ -# JSONvv - -JSON value validator - -## Overview - -This is a simple JSON schema validator library. It was created for Camoufox to validate passed user configurations. Because I found it useful for other projects, I decided to extract it into a separate library. - -JSONvv's syntax parser is written in pure Python. It does not rely on any dependencies. - -### Example - - - - - - - - - - -
ConfigurationValidator
- -```python -config = { - "username": "johndoe", - "email": "johndoe@example.com", - "age": 30, - "chat": "Hello world!", - "preferences": { - "notifications": True, - "theme": "dark" - }, - "allowed_commands": [ - "/help", "/time", "/weather" - ], - "location": [40.7128, -74.0060], - "hobbies": [ - { - "name": "Traveling", - "cities": ["Paris", "London"] - }, - { - "name": "reading", - "hours": { - "Sunday": 2, - "Monday": 3, - } - } - ] -} -``` - - - -```python -validator = { - "username": "str", # Basic username - "email": "str[/\S+@\S+\.\S+/]", # Validate emails - "age": "int[>=18]", # Age must be 18 or older - "chat": "str | nil", # Optional chat message - "preferences": { - "notifications": "bool", - "theme": "str[light, dark] | nil", # Optional theme - }, - # Commands must start with "/", but not contain "sudo" - "allowed_commands": "array[str[/^//] - str[/sudo/]]", - # Validate coordinate ranges - "location": "tuple[double[-90 - 90], double[-180 - 180]]", - # Handle an array of hobby types - "hobbies": "array[@traveling | @other, >=1]", - "@traveling": { - # Require 1 or more cities/countries iff name is "Traveling" - "*name,type": "str[Traveling]", - "*cities,countries": "array[str[A-Za-z*], >=1]", - }, - "@other": { - "name,type": "str - str[Traveling]", # Non-traveling types - # If hour(s) is specified, require days have >0 hours - "/hours?/": { - "*/day$/": "int[>0]" - } - } -} -``` - -
- -
- -Then, validate the configuration like this: - -```python -from jsonvv import JsonValidator, JvvRuntimeException - -val = JsonValidator(validator) -try: - val.validate(config) -except JvvRuntimeException as exc: - print("Failed:", exc) -else: - print('Config is valid!') -``` - ---- - -## Table of Contents - -- [Key Syntax](#key-syntax) - - [Regex patterns](#regex-patterns) - - [Lists of possible values](#lists-of-possible-values) - - [Required fields (`*`)](#required-fields-) - - [Grouping keys (`$`)](#grouping-keys-) -- [Supported Types](#supported-types) - - [String (`str`)](#string-str) - - [Integer (`int`)](#integer-int) - - [Double (`double`)](#double-double) - - [Boolean (`bool`)](#boolean-bool) - - [Array (`array`)](#array-array) - - [Tuple (`tuple`)](#tuple-tuple) - - [Nested Dictionaries](#nested-dictionaries) - - [Nil (`nil`)](#nil-nil) - - [Any (`any`)](#any-any) - - [Required fields (`*`)](#required-fields-) - - [Type References (`@`)](#type-references-) -- [Advanced Features](#advanced-features) - - [Subtracting Domains (`-`)](#subtracting-domains--) - - [Union Types (`|`)](#union-types-) - - [Conditional Ranges and Values](#conditional-ranges-and-values) -- [Error Handling](#error-handling) - ---- - -## Keys Syntax - -Dictionary keys can be specified in several possible ways: - -- `"key": "type"` -- `"key1,key2,key3": "type"` -- `"/key\d+/": "type"` -- `"*required_key": "type"` - -### Regex patterns - -To use regex in a key, wrap it in `/ ... /`. - -**Syntax:** - -```python -"/key\d+/": "type" -``` - -### Lists of possible values - -To specify a list of keys, use a comma-separated string. - -**Syntax:** - -```python -"key1,key2,key3": "type" -"/k[ey]{2}1/,key2": "type" -``` - -To escape a comma, use `!`. - -### Required fields (`*`) - -Fields marked with `*` are required. The validation will fail without them. - -**Syntax:** - -```python -"*key1": "type" -"*/key\d+/": "type" -``` - -### Grouping keys (`$`) - -Fields that end with `$group_name` are grouped together. If one of the keys is set, all of the keys in the group must also be set as well. - -**Syntax:** - -```python -"isEnabled$group1": "bool" -"value$group1": "int[>0]" -``` - -This will require both `value` is set if and only if `isEnabled` is set. - -Multiple `$` can be used to create more complex group dependencies. - ---- - -## Supported Types - -### String (`str`) - -Represents a string value. Optionally, you can specify a regex pattern that the string must match. - -**Syntax:** - -- Basic string: `"str"` -- With regex pattern: `"str[regex_pattern]"` -- The escape character for regex is `\`, and for commas is `_`. - -**Arguments:** - -- `regex_pattern`: A regular expression that the string must match. If not specified, any string is accepted. - -**Examples:** - -1. Basic string: - - ```python - "username": "str" - ``` - - Accepts any string value for the key `username`. - -2. String with regex pattern: - - ```python - "fullname": "str[/[A-Z][a-z]+ [A-Z][a-z]+/]" - ``` - - Accepts a string that matches the pattern of a first and last name starting with uppercase letters. - -### Integer (`int`) - -Represents an integer value. You can specify conditions like exact values, ranges, and inequalities. - -**Syntax:** - -- Basic integer: `"int"` -- With conditions: `"int[conditions]"` - -**Arguments:** - -- `conditions`: A comma-separated list of conditions. - -**Condition Operators:** - -- `==`: Equal to a specific value. -- `>=`: Greater than or equal to a value. -- `<=`: Less than or equal to a value. -- `>`: Greater than a value. -- `<`: Less than a value. -- `range`: A range between two values (inclusive). - -**Examples:** - -1. Basic integer: - - ```python - "age": "int" - ``` - - Accepts any integer value for the key `age`. - -2. Integer with conditions: - - ```python - "userage": "int[>=0, <=120]" - ``` - - Accepts integer values between 0 and 120 inclusive. - -3. Specific values and ranges - - ```python - "rating": "int[1-5]" - "rating": "int[1,2,3,4-5]" - ``` - - Accepts integer values 1, 2, 3, 4, or 5. - -4. Ranges with negative numbers: - - ```python - "rating": "int[-100 - -90]" - ``` - - Accepts integer values from -100 to -90. - -### Double (`double`) - -Represents a floating-point number. Supports the same conditions as integers. - -**Syntax:** - -- Basic double: `"double"` -- With conditions: `"double[conditions]"` - -**Arguments:** - -- `conditions`: A comma-separated list of conditions. - -**Examples:** - -1. Basic double: - - ```python - "price": "double" - ``` - - Accepts any floating-point number for the key `price`. - -2. Double with conditions: - - ```python - "percentage": "double[>=0.0,<=100.0]" - ``` - - Accepts double values between 0.0 and 100.0 inclusive. - -### Boolean (`bool`) - -Represents a boolean value (`True` or `False`). - -**Syntax:** - -```python -"isActive": "bool" -``` - -Accepts a boolean value for the key `isActive`. - -### Array (`array`) - -Represents a list of elements of a specified type. You can specify conditions on the length of the array. - -**Syntax:** - -- Basic array: `"array[element_type]"` -- With length conditions: `"array[element_type,length_conditions]"` - -**Arguments:** - -- `element_type`: The type of the elements in the array. -- `length_conditions`: Conditions on the array length (same as integer conditions). - -**Examples:** - -1. Basic array: - - ```python - "tags": "array[str]" - ``` - - Accepts a list of strings for the key `tags`. - -2. Array with length conditions: - - ```python - "scores": "array[int[>=0,<=100],>=1,<=5]" - ``` - - Accepts a list of 1 to 5 integers between 0 and 100 inclusive. - -3. Fixed-length array: - - ```python - "coordinates": "array[double, 2]" - ``` - - Accepts a list of exactly 2 double values. - -4. More complex restraints: - ```python - "coordinates": "array[array[int[>0]] - tuple[1, 1]], 2]" - ``` - -### Tuple (`tuple`) - -Represents a fixed-size sequence of elements of specified types. - -**Syntax:** - -```python -"tuple[element_type1, element_type2]" -``` - -**Arguments:** - -- `element_typeN`: The type of the Nth element in the tuple. - -**Examples:** - -1. Basic tuple: - - ```python - "point": "tuple[int, int]" - ``` - - Accepts a tuple or list of two integers. - -2. Tuple with mixed types: - - ```python - "userInfo": "tuple[str, int, bool]" - ``` - - Accepts a tuple of a string, an integer, and a boolean. - -### Nested Dictionaries - -Represents a nested dictionary structure. Dictionaries are defined using Python's dictionary syntax `{}` in the type definitions. - -**Syntax:** - -```python -"settings": { - "volume": "int[>=0,<=100]", - "brightness": "int[>=0,<=100]", - "mode": "str" -} -``` - -**Usage:** - -- Define the expected keys and their types within the dictionary. -- You can use all the supported types for the values. - -**Examples:** - -1. Nested dictionary: - - ```python - "user": { - "name": "str", - "age": "int[>=0]", - "preferences": { - "theme": "str", - "notifications": "bool" - } - } - ``` - - Defines a nested dictionary structure for the key `user`. - -### Nil (`nil`) - -Represents a `None` value. - -**Syntax:** - -```python -"optionalValue": "int | nil" -``` - -**Usage:** - -- Use `nil` to allow a value to be `None`. -- Often used with union types to specify optional values. - -### Any (`any`) - -Represents any value. - -**Syntax:** - -```python -"metadata": "any" -``` - -**Usage:** - -- Use `any` when any value is acceptable. -- Useful for keys where the value is not constrained. - -### Type References (`@`) - -Allows you to define reusable types and reference them. - -**Syntax:** - -- Define a named type: - - ```python - "@typeName": "type_definition" - ``` - -- Reference a named type: - - ```python - "key": "@typeName" - ``` - -**Examples:** - -1. Defining and using a named type: - - ```python - "@positiveInt": "int[>0]" - "userId": "@positiveInt" - ``` - - Defines a reusable type `@positiveInt` and uses it for the key `userId`. - ---- - -## Advanced Features - -### Subtracting Domains (`-`) - -Allows you to specify that a value should not match a certain type or condition. - -**Syntax:** - -```python -"typeA - typeB" -``` - -**Usage:** - -- The value must match `typeA` but not `typeB`. - -**Examples:** - -1. Excluding certain strings: - - ```python - "message": "str - str[.*error.*]" - ``` - - Accepts any string that does not match the regex pattern `.*error.*`. - -2. Excluding a range of numbers: - - ```python - "score": "int[0-100] - int[>=90]" - ``` - - Accepts integers between 0 and 100, excluding values greater than or equal to 90. - -3. Excluding multiple types: - - ```python - "score": "int[>0,<100] - int[>90] - int[<10]" - # Union, then subtraction: - "score": "int[>0,<100] - int[>90] | int[<10]" - "score": "int[>0,<100] - (int[>90] | int[<10])" # same thing - # Use parenthesis to run subtraction first - "score": "int[>0,<50] | (int[<100] - int[<10])" - "score": "(int[<100] - int[<10]) | int[>0,<50]" - ``` - - **Note**: Union is handled before subtraction. - -4. Allowing all but a specific value: - - ```python - "specialNumber": "any - int[0]" - ``` - -### Union Types (`|`) - -Allows you to specify that a value can be one of multiple types. - -**Syntax:** - -```python -"typeA | typeB | typeC" -``` - -**Usage:** - -- The value must match at least one of the specified types. - -**Examples:** - -1. Multiple possible types: - - ```python - "data": "int | str | bool" - ``` - - Accepts an integer, string, or boolean value for the key `data`. - -2. Combining with arrays: - - ```python - "mixedList": "array[int | str]" - ``` - - Accepts a list of integers or strings. - -### Conditional Ranges and Values - -Specifies conditions that values must satisfy, including ranges and specific values. - -**Syntax:** - -- Greater than: `">value"` -- Less than: `"=value"` -- Less than or equal to: `"<="value"` -- Range: `"start-end"` -- Specific values: `"value1,value2,value3"` - -**Examples:** - -1. Integer conditions: - - ```python - "level": "int[>=1,<=10]" - ``` - - Accepts integers from 1 to 10 inclusive. - -2. Double with range: - - ```python - "latitude": "double[-90.0 - 90.0]" - ``` - - Accepts doubles between -90.0 and 90.0 inclusive. - -3. Specific values: - - ```python - "status": "int[1,2,3]" - ``` - - Accepts integers that are either 1, 2, or 3. - ---- - -## Error Handling - -```mermaid -graph TD - Exception --> JvvException - JvvException --> JvvRuntimeException - JvvException --> JvvSyntaxError - - JvvRuntimeException --> UnknownProperty["UnknownProperty
Raised when a key in config
isn't defined in property types
"] - JvvRuntimeException --> InvalidPropertyType["InvalidPropertyType
Raised when a value doesn't
match its type definition
"] - InvalidPropertyType --> MissingRequiredKey["MissingRequiredKey
Raised when a required key
is missing from config
"] - MissingRequiredKey --> MissingGroupKey["MissingGroupKey
Raised when some keys in a
property group are missing
"] - - JvvSyntaxError --> PropertySyntaxError["PropertySyntaxError
Raised when property type
definitions have syntax errors
"] - - classDef base fill:#eee,stroke:#333,stroke-width:2px; - classDef jvv fill:#d4e6f1,stroke:#2874a6,stroke-width:2px; - classDef runtime fill:#d5f5e3,stroke:#196f3d,stroke-width:2px; - classDef syntax fill:#fdebd0,stroke:#b9770e,stroke-width:2px; - classDef error fill:#fadbd8,stroke:#943126,stroke-width:2px; - - class Exception base; - class JvvException jvv; - class JvvRuntimeException,JvvSyntaxError runtime; - class PropertySyntaxError syntax; - class UnknownProperty,InvalidPropertyType,MissingRequiredKey,MissingGroupKey error; -``` - ---- - -### Types - -- **str**: Basic string type. - - - Arguments: - - `regex_pattern` (optional): A regex pattern the string must match. - - Example: `"str[^[A-Za-z]+$]"` - -- **int**: Integer type with conditions. - - - Arguments: - - `conditions`: Inequalities (`>=`, `<=`, `>`, `<`), specific values (`value1,value2`), ranges (`start-end`). - - Example: `"int[>=0,<=100]"` - -- **double**: Double (floating-point) type with conditions. - - - Arguments: - - Same as `int`. - - Example: `"double[>0.0]"` - -- **bool**: Boolean type. - - - Arguments: None. - - Example: `"bool"` - -- **array**: Array (list) of elements of a specified type. - - - Arguments: - - `element_type`: Type of elements in the array. - - `length_conditions` (optional): Conditions on the array length. - - Example: `"array[int[>=0],>=1,<=10]"` - -- **tuple**: Fixed-size sequence of elements of specified types. - - - Arguments: - - List of element types. - - Example: `"tuple[str, int, bool]"` - -- **nil**: Represents a `None` value. - - - Arguments: None. - - Example: `"nil"` - -- **any**: Accepts any value. - - - Arguments: None. - - Example: `"any"` - -- **Type References**: Reusable type definitions. - - Arguments: - - `@typeName`: Reference to a named type. - - Example: - - Define: `"@positiveInt": "int[>0]"` - - Use: `"userId": "@positiveInt"` - -### Type Combinations - -- **Union Types** (`|`): Value must match one of multiple types. - - - Syntax: `"typeA | typeB"` - - Example: `"str | int"` - -- **Subtracting Domains** (`-`): Value must match `typeA` but not `typeB`. - - Syntax: `"typeA - typeB"` - - Example: `"int - int[13]"` (any integer except 13) - -### Escaping Characters - -- `!`: Escapes commas, slashes, and other jsonvv characters within strings. -- `\`: Escapes within a regex pattern. diff --git a/jsonvv/jsonvv/__init__.py b/jsonvv/jsonvv/__init__.py deleted file mode 100644 index c63e435..0000000 --- a/jsonvv/jsonvv/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -from .exceptions import ( - InvalidPropertyType, - JvvException, - JvvRuntimeException, - JvvSyntaxError, - MissingRequiredKey, - PropertySyntaxError, - UnknownProperty, -) -from .validator import JsonValidator - -__all__ = [ - 'JvvRuntimeException', - 'JvvSyntaxError', - 'PropertySyntaxError', - 'JsonValidator', - 'JvvException', - 'InvalidPropertyType', - 'UnknownProperty', - 'MissingRequiredKey', -] diff --git a/jsonvv/jsonvv/__main__.py b/jsonvv/jsonvv/__main__.py deleted file mode 100644 index 2d9fa8b..0000000 --- a/jsonvv/jsonvv/__main__.py +++ /dev/null @@ -1,70 +0,0 @@ -import argparse -import json -import sys -from pathlib import Path -from typing import Any, Dict - -from jsonvv.exceptions import InvalidPropertyType, JvvSyntaxError, UnknownProperty -from jsonvv.validator import JsonValidator - - -def load_json(file_path: Path) -> Dict[str, Any]: - """ - Load and parse a JSON file. - """ - try: - with open(file_path) as f: - return json.load(f) - except json.JSONDecodeError as e: - raise ValueError(f"Invalid JSON in {file_path}: {e}") - except FileNotFoundError: - raise ValueError(f"File not found: {file_path}") - - -def main(): - """JSON Value Validator - Validate JSON data against a schema.""" - parser = argparse.ArgumentParser( - description="JSON Value Validator - Validate JSON data against a schema." - ) - parser.add_argument( - 'properties_file', type=Path, help='JSON file containing the property type definitions' - ) - parser.add_argument( - '-i', '--input', type=Path, help='JSON file containing the data to validate' - ) - parser.add_argument( - '--check', action='store_true', help='Check if the properties file is valid' - ) - - args = parser.parse_args() - - try: - # Load property types - property_types = load_json(args.properties_file) - validator = JsonValidator(property_types) - - if args.check: - print("✓ Property types are valid") - return - - if not args.input: - parser.error("Either --input or --check must be specified") - - # Load and validate data - data = load_json(args.input) - validator.validate(data) - print("✓ Data is valid") - - except (InvalidPropertyType, UnknownProperty) as e: - print(f"Validation Error: {e}", file=sys.stderr) - sys.exit(1) - except JvvSyntaxError as e: - print(f"Syntax Error: {e}", file=sys.stderr) - sys.exit(1) - except ValueError as e: - print(f"File Error: {e}", file=sys.stderr) - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/jsonvv/jsonvv/exceptions.py b/jsonvv/jsonvv/exceptions.py deleted file mode 100644 index 9001923..0000000 --- a/jsonvv/jsonvv/exceptions.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Exception classes for jsonvv""" - - -class JvvException(Exception): - pass - - -class JvvRuntimeException(JvvException): - pass - - -class JvvSyntaxError(JvvException): - pass - - -class UnknownProperty(JvvRuntimeException, ValueError): - pass - - -class InvalidPropertyType(JvvRuntimeException, TypeError): - pass - - -class MissingRequiredKey(InvalidPropertyType): - pass - - -class MissingGroupKey(MissingRequiredKey): - pass - - -class PropertySyntaxError(JvvSyntaxError): - pass diff --git a/jsonvv/jsonvv/parser.py b/jsonvv/jsonvv/parser.py deleted file mode 100644 index 5b95ddc..0000000 --- a/jsonvv/jsonvv/parser.py +++ /dev/null @@ -1,309 +0,0 @@ -from dataclasses import dataclass -from typing import Any, Dict, List - -from .exceptions import InvalidPropertyType -from .strings import string_validator -from .types import ( - AnyType, - ArrayType, - BaseType, - BoolType, - DoubleType, - IntType, - NilType, - StringType, - SubtractionType, - TupleType, - Type, - UnionType, -) - - -class Parser: - def __init__(self, type_str: str): - self.type_str = type_str - self.pos = 0 - self.length = len(type_str) - - def parse(self) -> Type: - """Main entry point""" - result = self.parse_subtraction() # Start with subtraction instead of union - self.skip_whitespace() - if self.pos < self.length: - raise RuntimeError(f"Unexpected character at position {self.pos}") - return result - - def parse_union(self) -> Type: - """Handles type1 | type2 | type3""" - types = [self.parse_term()] # Parse first term - - while self.pos < self.length: - self.skip_whitespace() - if not self.match('|'): - break - types.append(self.parse_term()) # Parse additional terms - - return types[0] if len(types) == 1 else UnionType(types) - - def parse_subtraction(self) -> Type: - """Handles type1 - type2""" - left = self.parse_union() # Start with union - - while self.pos < self.length: - self.skip_whitespace() - if not self.match('-'): - break - right = self.parse_union() # Parse right side as union - left = SubtractionType(left, right) - - return left - - def parse_term(self) -> Type: - """Handles basic terms and parenthesized expressions""" - self.skip_whitespace() - - if self.match('('): - type_obj = self.parse_subtraction() # Parse subtraction inside parens - if not self.match(')'): - raise RuntimeError("Unclosed parenthesis") - return type_obj - - return self.parse_basic_type() - - def parse_basic_type(self) -> Type: - """Handles basic types with conditions""" - name = self.parse_identifier() - - # Special handling for array type - if name == 'array': - return self.parse_array_type() - - # Special handling for tuple type - if name == 'tuple': - # Don't advance position, let parse_tuple_type handle it - return self.parse_tuple_type() - - conditions = None - self.skip_whitespace() - - if self.match('['): - start = self.pos - # For all types, just capture everything until the closing bracket - bracket_count = 1 # Track nested brackets - while self.pos < self.length: - if self.type_str[self.pos] == '[': - bracket_count += 1 - elif self.type_str[self.pos] == ']': - bracket_count -= 1 - if bracket_count == 0: - break - self.pos += 1 - - if bracket_count > 0: - raise RuntimeError("Unclosed '['") - conditions = self.type_str[start : self.pos] - - if not self.match(']'): - raise RuntimeError("Expected ']'") - - # Return appropriate type based on name - if name == 'str': - return StringType(conditions) - elif name == 'int': - return IntType(conditions) - elif name == 'double': - return DoubleType(conditions) - elif name == 'bool': - return BoolType() - elif name == 'any': - return AnyType() - elif name == 'nil': - return NilType() # Add this type - elif name == 'tuple': - return self.parse_tuple_type() - elif name.startswith('@'): - return ReferenceType(name[1:]) - return BaseType(name, conditions) - - def peek(self, char: str) -> bool: - """Looks ahead for a character without advancing position""" - self.skip_whitespace() - return self.pos < self.length and self.type_str[self.pos] == char - - def parse_array_type(self) -> Type: - """Handles array[type, length?]""" - if not self.match('['): - return ArrayType(AnyType(), None) # Default array type - - # Parse the element type (which could be a complex type) - element_type = self.parse_subtraction() # Start with subtraction to handle all cases - - length_conditions = None - self.skip_whitespace() - - # Check for length conditions after comma - if self.match(','): - self.skip_whitespace() - start = self.pos - while self.pos < self.length and self.type_str[self.pos] != ']': - self.pos += 1 - if self.pos >= self.length: - raise RuntimeError("Unclosed array type") - length_conditions = self.type_str[start : self.pos].strip() - - if not self.match(']'): - raise RuntimeError("Expected ']' in array type") - - return ArrayType(element_type, length_conditions) - - def parse_tuple_type(self) -> Type: - """Handles tuple[type1, type2, ...]""" - - if not self.match('['): - raise RuntimeError("Expected '[' after 'tuple'") - - types = [] - while True: - self.skip_whitespace() - if self.match(']'): - break - - # Parse complex type expressions within tuple arguments - type_obj = self.parse_subtraction() # Start with subtraction to handle all operations - types.append(type_obj) - - self.skip_whitespace() - if not self.match(','): - if self.match(']'): - break - raise RuntimeError("Expected ',' or ']' in tuple type") - - return TupleType(types) - - def parse_identifier(self) -> str: - """Parses an identifier""" - self.skip_whitespace() - start = self.pos - - # Only consume alphanumeric and underscore characters - while self.pos < self.length and ( - self.type_str[self.pos].isalnum() or self.type_str[self.pos] in '_.@!' - ): - self.pos += 1 - - if start == self.pos: - raise RuntimeError(f'Expected identifier at position {self.pos}') - - result = self.type_str[start : self.pos] - return result - - def skip_whitespace(self) -> None: - """Skips whitespace characters""" - while self.pos < self.length and self.type_str[self.pos].isspace(): - self.pos += 1 - - def match(self, char: str) -> bool: - """Tries to match a character, advances position if matched""" - self.skip_whitespace() - if self.pos < self.length and self.type_str[self.pos] == char: - self.pos += 1 - return True - return False - - def peek_word(self, word: str) -> bool: - """Looks ahead for a word without advancing position""" - self.skip_whitespace() - return ( - self.pos + len(word) <= self.length - and self.type_str[self.pos : self.pos + len(word)] == word - and ( - self.pos + len(word) == self.length - or not self.type_str[self.pos + len(word)].isalnum() - ) - ) - - -''' -Python's import system is a pain, -so I'm moving DictType and ReferenceType here. -''' - - -@dataclass -class DictType(Type): - type_dict: Dict[str, Any] - type_registry: Dict[str, Any] - - def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None: - if not isinstance(value, dict): - raise InvalidPropertyType(f"Expected dict at {'.'.join(path)}, got {type(value)}") - - # Track matched patterns and required keys - any_pattern_matched = False - required_patterns = { - pattern[1:]: False for pattern in self.type_dict if pattern.startswith('*') - } - - for key, val in value.items(): - pattern_matched = False - for pattern, type_def in self.type_dict.items(): - # Strip * for required patterns when matching - match_pattern = pattern[1:] if pattern.startswith('*') else pattern - - if string_validator(key, match_pattern): - pattern_matched = True - any_pattern_matched = True - - # Mark required pattern as found - if pattern.startswith('*'): - required_patterns[match_pattern] = True - - # Parse the type definition string into a Type object - expected_type = parse_type_def(type_def, type_registry) - expected_type.validate(val, path + [key], type_registry) - - if not pattern_matched: - raise InvalidPropertyType( - f"Key {key} at {'.'.join(path)} does not match any allowed patterns" - ) - - # Check if all required patterns were matched - missing_required = [pattern for pattern, found in required_patterns.items() if not found] - if missing_required: - raise InvalidPropertyType( - f"Missing required properties matching patterns: {', '.join(missing_required)} at {'.'.join(path)}" - ) - - if not any_pattern_matched: - raise InvalidPropertyType(f"No properties at {'.'.join(path)} matched any patterns") - - -@dataclass -class ReferenceType(Type): - name: str - - def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None: - if self.name not in type_registry: - raise RuntimeError(f"Unknown type reference: @{self.name}") - - ref_type = type_registry[self.name] - - if isinstance(ref_type, dict): - # Create a DictType for dictionary references - dict_type = DictType(ref_type, type_registry) - dict_type.validate(value, path, type_registry) - else: - # For non-dictionary types - ref_type.validate(value, path, type_registry) - - def __str__(self) -> str: - return f"@{self.name}" - - -def parse_type_def(type_def: Any, type_registry: Dict[str, Type]) -> Type: - if isinstance(type_def, str): - parser = Parser(type_def) - return parser.parse() - elif isinstance(type_def, dict): - return DictType(type_def, type_registry) - raise InvalidPropertyType(f"Invalid type definition: {type_def}") diff --git a/jsonvv/jsonvv/strings.py b/jsonvv/jsonvv/strings.py deleted file mode 100644 index 56d202b..0000000 --- a/jsonvv/jsonvv/strings.py +++ /dev/null @@ -1,64 +0,0 @@ -import re -from typing import List - - -class StringValidator: - def __init__(self, pattern: str): - self.pattern = pattern - self.patterns = self._split_patterns(pattern) - - def _split_patterns(self, p: str) -> List[str]: - patterns = [] - current = [] - in_regex = False - i = 0 - - while i < len(p): - if p[i] == '/' and (i == 0 or p[i - 1] != '!'): - in_regex = not in_regex - current.append(p[i]) - elif p[i] == ',' and not in_regex: - # Check if comma is escaped - if i > 0 and p[i - 1] == '!': - current.append(',') - else: - # End of pattern - patterns.append(''.join(current)) - current = [] - else: - current.append(p[i]) - i += 1 - - if current: - patterns.append(''.join(current)) - - result = [p.strip() for p in patterns if p.strip()] - return result - - def _is_regex_pattern(self, p: str) -> bool: - is_regex = p.startswith('/') and p.endswith('/') and not p.endswith('!/') - return is_regex - - def _clean_literal_pattern(self, p: str) -> str: - return re.sub(r'!(.)', r'\1', p) - - def validate(self, value: str) -> bool: - for p in self.patterns: - p = self._clean_literal_pattern(p) - if self._is_regex_pattern(p): - regex = p[1:-1] - match = bool(re.match(regex, value)) - if match: - return True - else: - match = value == p - if match: - return True - - return False - - -def string_validator(value: str, pattern: str) -> bool: - validator = StringValidator(pattern) - result = validator.validate(value) - return result diff --git a/jsonvv/jsonvv/types.py b/jsonvv/jsonvv/types.py deleted file mode 100644 index 21357bd..0000000 --- a/jsonvv/jsonvv/types.py +++ /dev/null @@ -1,262 +0,0 @@ -from abc import ABC, abstractmethod -from dataclasses import dataclass -from typing import Any, Dict, List, Optional, Union - -from .exceptions import InvalidPropertyType -from .strings import string_validator - -TYPE_NAMES = {'array', 'tuple', 'str', 'int', 'double', 'bool', 'any', 'nil', 'tuple'} - - -class Type(ABC): - @abstractmethod - def validate(self, value: Any, path: List[str], type_registry: Dict[str, 'Type']) -> None: - pass - - -@dataclass -class BaseType(Type): - """Base class for all types""" - - name: str - conditions: Optional[str] = None - - def __post_init__(self): - # Raise error early - if not self.name.startswith('@') and self.name not in TYPE_NAMES: - raise InvalidPropertyType(f'Unknown base type {self.name}') - - def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None: - if self.name in type_registry: - type_registry[self.name].validate(value, path, type_registry) - else: - raise RuntimeError(f'Unknown base type {self.name}') - - -@dataclass -class NilType(Type): - """Represents a nil/null type""" - - def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None: - if value is not None: - raise InvalidPropertyType( - f"Invalid value at {'.'.join(path)}: expected nil, got {value}" - ) - - def __str__(self) -> str: - return "nil" - - -@dataclass -class StringType(Type): - pattern: Optional[str] = None - - def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None: - if not isinstance(value, str): - raise InvalidPropertyType( - f"Invalid value at {'.'.join(path)}: expected string, got {type(value).__name__}" - ) - - if self.pattern: - if not string_validator(value, self.pattern): - raise InvalidPropertyType( - f"Invalid value at {'.'.join(path)}: {value} does not match pattern '{self.pattern}'" - ) - - def __str__(self) -> str: - return f"str[{self.pattern}]" if self.pattern else "str" - - -@dataclass -class NumericalType(Type): - conditions: Optional[str] = None - numeric_type: Type = float # Default to float - type_name: str = "number" # For error messages - - def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None: - allowed_types = (int, float) if self.numeric_type is float else (int,) - if not isinstance(value, allowed_types): - raise InvalidPropertyType( - f"Invalid value at {'.'.join(path)}: expected {self.type_name}, got {type(value).__name__}" - ) - if self.conditions and not self._check_conditions(self.numeric_type(value)): - raise InvalidPropertyType( - f"Invalid value at {'.'.join(path)}: {value} does not match conditions '{self.conditions}'" - ) - - def _check_conditions(self, value: Union[int, float]) -> bool: - if not self.conditions: - return True - - # Split by comma and handle each condition - conditions = [c.strip() for c in self.conditions.split(',')] - - for condition in conditions: - try: - # Handle comparisons - if '>=' in condition: - if value >= self.numeric_type(condition.replace('>=', '')): - return True - elif '<=' in condition: - if value <= self.numeric_type(condition.replace('<=', '')): - return True - elif '>' in condition: - if value > self.numeric_type(condition.replace('>', '')): - return True - elif '<' in condition: - if value < self.numeric_type(condition.replace('<', '')): - return True - # Handle ranges (e.g., "1.5-5.5") - elif '-' in condition[1:]: - # split by the -, ignoring the first character - range_s, range_e = condition[1:].split('-', 1) - range_s = self.numeric_type(condition[0] + range_s) - range_e = self.numeric_type(range_e) - if range_s <= value <= range_e: - return True - # Handle single values - else: - if value == self.numeric_type(condition): - return True - except ValueError: - continue - - return False - - def __str__(self) -> str: - return f"{self.type_name}[{self.conditions}]" if self.conditions else self.type_name - - -@dataclass -class IntType(NumericalType): - def __init__(self, conditions: Optional[str] = None): - super().__init__(conditions=conditions, numeric_type=int, type_name="int") - - -@dataclass -class DoubleType(NumericalType): - def __init__(self, conditions: Optional[str] = None): - super().__init__(conditions=conditions, numeric_type=float, type_name="double") - - -@dataclass -class AnyType(Type): - def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None: - # Any type accepts all values - pass - - def __str__(self) -> str: - return "any" - - -@dataclass -class BoolType(Type): - def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None: - if not isinstance(value, bool): - raise InvalidPropertyType( - f"Invalid value at {'.'.join(path)}: expected bool, got {type(value).__name__}" - ) - - -@dataclass -class ArrayType(Type): - element_type: Type - length_conditions: Optional[str] = None - - def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None: - if not isinstance(value, list): - raise InvalidPropertyType( - f"Invalid value at {'.'.join(path)}: expected array, got {type(value).__name__}" - ) - - if self.length_conditions: - array_len = len(value) - length_validator = IntType(self.length_conditions) - try: - length_validator._check_conditions(array_len) - except Exception: - raise InvalidPropertyType( - f"Invalid array length at {'.'.join(path)}: got length {array_len}" - ) - - for i, item in enumerate(value): - self.element_type.validate(item, path + [str(i)], type_registry) - - -@dataclass -class TupleType(Type): - element_types: List[Type] - - def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None: - if not isinstance(value, (list, tuple)): - raise InvalidPropertyType( - f"Invalid value at {'.'.join(path)}: expected tuple, got {type(value).__name__}" - ) - - if len(value) != len(self.element_types): - raise InvalidPropertyType( - f"Invalid tuple length at {'.'.join(path)}: expected {len(self.element_types)}, got {len(value)}" - ) - - for i, (item, expected_type) in enumerate(zip(value, self.element_types)): - expected_type.validate(item, path + [str(i)], type_registry) - - -@dataclass -class UnionType(Type): - types: List[Type] - - def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None: - errors = [] - for t in self.types: - try: - t.validate(value, path, type_registry) - return # If any type validates successfully, we're done - except InvalidPropertyType as e: - errors.append(str(e)) - - # If we get here, none of the types validated - raise InvalidPropertyType( - f"Invalid value at {'.'.join(path)}: {value} does not match any of the allowed types" - ) - - def __str__(self) -> str: - return f"({' | '.join(str(t) for t in self.types)})" - - -@dataclass -class SubtractionType(Type): - base_type: Type - subtracted_type: Type - - def validate(self, value: Any, path: List[str], type_registry: Dict[str, Type]) -> None: - path_str = '.'.join(path) - - # First check if value matches base type - matches_base = True - try: - self.base_type.validate(value, path, type_registry) - except InvalidPropertyType: - matches_base = False - raise - - # Then check if value matches subtracted type - matches_subtracted = True - try: - self.subtracted_type.validate(value, path, type_registry) - matches_subtracted = True - except InvalidPropertyType: - matches_subtracted = False - - # Final validation decision - if matches_base and matches_subtracted: - raise InvalidPropertyType(f"Invalid value at {path_str}: {value} matches excluded type") - elif matches_base and not matches_subtracted: - return - else: - raise InvalidPropertyType( - f"Invalid value at {path_str}: {value} does not match base type" - ) - - def __str__(self) -> str: - return f"({self.base_type} - {self.subtracted_type})" diff --git a/jsonvv/jsonvv/validator.py b/jsonvv/jsonvv/validator.py deleted file mode 100644 index ec09ae9..0000000 --- a/jsonvv/jsonvv/validator.py +++ /dev/null @@ -1,170 +0,0 @@ -from typing import Any, Dict, List, Optional - -from .exceptions import ( - MissingGroupKey, - MissingRequiredKey, - PropertySyntaxError, - UnknownProperty, -) -from .parser import parse_type_def -from .strings import string_validator -from .types import Type - - -class JsonValidator: - def __init__(self, property_types): - self.property_types = property_types - # Create a registry for reference types and parsed type definitions - self.type_registry = {} - self.parsed_types = {} - # Track property groups - self.groups: Dict[str, List[str]] = {} - # Validate and pre-parse all type definitions - self.parse_types(property_types) - - def validate(self, config_map): - # First validate groups - self.validate_groups(config_map) - # Then validate the rest - validate_config(config_map, self.property_types, self.type_registry, self.parsed_types) - - def parse_types(self, property_types: Dict[str, Any], path: str = ""): - """Validates and pre-parses all type definitions.""" - for key, value in property_types.items(): - current_path = f"{path}.{key}" if path else key - - # Register reference types - if key.startswith('@'): - if len(key) == 1: - raise PropertySyntaxError( - f"Invalid key '{current_path}': '@' must be followed by a reference name" - ) - self.type_registry[key[1:]] = value - - # Validate key syntax for required properties - if key.startswith('*') and len(key) == 1: - raise PropertySyntaxError( - f"Invalid key '{current_path}': '*' must be followed by a property name" - ) - - # Register group dependencies - orig_key: Optional[str] = None - while (idx := key.rfind('$')) != -1: - # Get the original key before all $ - if orig_key is None: - orig_key = key.split('$', 1)[0] - # Add to group registry - key, group = key[:idx], key[idx + 1 :] - if group not in self.groups: - self.groups[group] = [] - self.groups[group].append(orig_key) - - if isinstance(value, dict): - # Recursively validate and parse nested dictionaries - self.parse_types(value, current_path) - elif isinstance(value, str): - try: - # Pre-parse the type definition and store it - self.parsed_types[current_path] = parse_type_def(value, self.type_registry) - except Exception as e: - raise PropertySyntaxError( - f"Invalid type definition for '{current_path}': {str(e)}" - ) - else: - raise PropertySyntaxError( - f"Invalid type definition for '{current_path}': must be a string or dictionary" - ) - - def validate_groups(self, config_map: Dict[str, Any]) -> None: - """Validates that grouped properties are all present or all absent.""" - group_presence: Dict[str, bool] = {} - - # Check which groups have any properties present - for group, props in self.groups.items(): - group_presence[group] = any(prop in config_map for prop in props) - - # Validate group completeness - for group, is_present in group_presence.items(): - props = self.groups[group] - if is_present: - # If any property in group exists, all must exist - missing = [prop for prop in props if prop not in config_map] - if missing: - raise MissingGroupKey( - f"Incomplete property group ${group}: missing {', '.join(missing)}" - ) - else: - # If no property in group exists, none should exist - present = [prop for prop in props if prop in config_map] - if present: - raise MissingGroupKey( - f"Incomplete property group ${group}: found {', '.join(present)} but missing {', '.join(set(props) - set(present))}" - ) - - -def validate_config( - config_map: Dict[str, Any], - property_types: Dict[str, Any], - type_registry: Dict[str, Type], - parsed_types: Dict[str, Type], - parent_registry: Dict[str, Type] = None, - path: str = "", -) -> None: - """Validates a configuration map against property types.""" - - # Create a new registry for this scope, inheriting from parent if it exists - local_registry = dict(parent_registry or type_registry) - - # Track required properties - required_props = {key[1:]: False for key in property_types if key.startswith('*')} - - # Validate each property in config - for key, value in config_map.items(): - type_def = None - current_path = f"{path}.{key}" if path else key - - # Strip group suffix for type lookup - lookup_key = key.split('$')[0] if '$' in key else key - - if lookup_key in property_types: - type_def = property_types[lookup_key] - - # If the value is a dict and type_def is also a dict, recurse with new scope - if isinstance(value, dict) and isinstance(type_def, dict): - validate_config( - value, type_def, type_registry, parsed_types, local_registry, current_path - ) - continue - - elif '*' + lookup_key in property_types: - type_def = property_types['*' + lookup_key] - required_props[lookup_key] = True - else: - # Check pattern matches - for pattern, pattern_type in property_types.items(): - if pattern.startswith('@') or pattern.startswith('*'): - continue - pattern_base = pattern.split('$')[0] if '$' in pattern else pattern - if string_validator(lookup_key, pattern_base): - type_def = pattern_type - current_path = f"{path}.{pattern}" if path else pattern - break - - if type_def is None: - raise UnknownProperty(f"Unknown property: {key}") - - # Use pre-parsed type if available, otherwise parse it - expected_type = parsed_types.get(current_path) - if expected_type is None: - expected_type = parse_type_def(type_def, local_registry) - expected_type.validate(value, [key], local_registry) - - # Check for missing required properties - missing_required = [key for key, found in required_props.items() if not found] - if missing_required: - raise MissingRequiredKey(f"Missing required properties: {', '.join(missing_required)}") - - # Check for missing required properties - missing_required = [key for key, found in required_props.items() if not found] - if missing_required: - raise MissingRequiredKey(f"Missing required properties: {', '.join(missing_required)}") diff --git a/jsonvv/publish.sh b/jsonvv/publish.sh deleted file mode 100644 index d09aeec..0000000 --- a/jsonvv/publish.sh +++ /dev/null @@ -1,12 +0,0 @@ -rm -rf ./dist - -vermin . --eval-annotations --target=3.8 --violations jsonvv/ || exit 1 - -python -m build -twine check dist/* - -read -p "Confirm publish? (y/n) " -n 1 -r -echo -if [[ $REPLY =~ ^[Yy]$ ]]; then - twine upload dist/* -fi diff --git a/jsonvv/pyproject.toml b/jsonvv/pyproject.toml deleted file mode 100644 index 7806339..0000000 --- a/jsonvv/pyproject.toml +++ /dev/null @@ -1,25 +0,0 @@ -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" - -[tool.poetry] -name = "jsonvv" -version = "0.2.2" -description = "JSON value validator" -authors = ["daijro "] -license = "MIT" -repository = "https://github.com/daijro/camoufox" -homepage = "https://github.com/daijro/camoufox/tree/main/pythonlib/jsonvv" -readme = "README.md" -keywords = [ - "json", - "validator", - "validation", - "typing", -] - -[tool.poetry.dependencies] -python = "^3.8" - -[tool.poetry.scripts] -jsonvv = "jsonvv.__main__:main" diff --git a/settings/omegafox.jvv b/settings/omegafox.jvv deleted file mode 100644 index 7791fe9..0000000 --- a/settings/omegafox.jvv +++ /dev/null @@ -1,308 +0,0 @@ -{ - "navigator.userAgent$__UA": "str", - "navigator.appVersion$__UA": "str", - "navigator.platform$__UA": "str", - "navigator.oscpu$__UA": "str", - - "navigator.appCodeName$__PROD_CODE": "str", - "navigator.appName$__PROD_CODE": "str", - "navigator.product$__PROD_CODE": "str", - "navigator.productSub": "str[/^\\d+$/]", - "navigator.buildID": "str[/^\\d+$/]", - - "screen.height$__SC": "int[>0]", - "screen.width$__SC": "int[>0]", - "screen.availHeight$__SC": "int[>=0]", - "screen.availWidth$__SC": "int[>=0]", - "screen.availTop": "int[>=0]", - "screen.availLeft": "int[>=0]", - "locale:language$__LOCALE": "str", - "locale:region$__LOCALE": "str", - "locale:script": "str", - "geolocation:latitude$__GEO": "double[-90 - 90]", - "geolocation:longitude$__GEO": "double[-180 - 180]", - "geolocation:accuracy": "double[>=0]", - "timezone": "str[/^[\\w_]+/[\\w_]+$/]", - - "locale:all": "str", - "headers.Accept-Language": "str", - "navigator.language": "str", - "navigator.languages": "array[str]", - - "headers.User-Agent": "str", - "headers.Accept-Encoding": "str", - "navigator.doNotTrack": "str[0, 1, unspecified]", - "navigator.hardwareConcurrency": "int[>0]", - "navigator.maxTouchPoints": "int[>=0]", - "navigator.cookieEnabled": "bool", - "navigator.globalPrivacyControl": "bool", - "navigator.onLine": "bool", - "window.history.length": "int[>=0]", - "pdfViewerEnabled": "bool", - - "window.outerHeight$__W_OUTER": "int[>0]", - "window.outerWidth$__W_OUTER": "int[>0]", - "window.innerHeight$__W_INNER": "int[>0]", - "window.innerWidth$__W_INNER": "int[>0]", - - "screen.colorDepth": "int[>0]", - "screen.pixelDepth": "int[>0]", - "screen.pageXOffset": "double", - "screen.pageYOffset": "double", - "window.scrollMinX": "int", - "window.scrollMinY": "int", - "window.scrollMaxX": "int", - "window.scrollMaxY": "int", - "window.screenX": "int", - "window.screenY": "int", - "window.devicePixelRatio": "double[>0]", - - "document.body.clientWidth$__DOC_BODY": "int[>=0]", - "document.body.clientHeight$__DOC_BODY": "int[>=0]", - "document.body.clientTop": "int", - "document.body.clientLeft": "int", - - "webrtc:ipv4": "@IPV4", - "webrtc:ipv6": "@IPV6", - "webrtc:localipv4": "@IPV4", - "webrtc:localipv6": "@IPV6", - - "@IPV4": "str[/^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$/]", - "@IPV6": "str[/^(([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4})$/]", - - "battery:charging$__BATTERY": "bool", - "battery:chargingTime$__BATTERY": "double[>=0]", - "battery:dischargingTime$__BATTERY": "double[>=0]", - "battery:level$__BATTERY": "double[>0]", - - "fonts": "array[str]", - "fonts:spacing_seed": "int[>=0]", - - "AudioContext:sampleRate": "int[>=0]", - "AudioContext:outputLatency": "double[>=0]", - "AudioContext:maxChannelCount": "int[>=0]", - - "mediaDevices:micros": "int[>=0]", - "mediaDevices:webcams": "int[>=0]", - "mediaDevices:speakers": "int[>=0]", - "mediaDevices:enabled": "bool", - - "webGl:renderer$__WEBGL": "str", - "webGl:vendor$__WEBGL": "str", - - "webGl:supportedExtensions": "array[str[/^[\\w_]+$/]]", - "webGl2:supportedExtensions": "array[str[/^[\\w_]+$/]]", - - "webGl:parameters": "@WEBGL_PARAMS", - "webGl2:parameters": "@WEBGL_PARAMS", - "webGl:parameters:blockIfNotDefined": "bool", - "webGl2:parameters:blockIfNotDefined": "bool", - - "webGl:shaderPrecisionFormats": "@WEBGL_SHADER_PRECISION_FORMATS", - "webGl2:shaderPrecisionFormats": "@WEBGL_SHADER_PRECISION_FORMATS", - "webGl:shaderPrecisionFormats:blockIfNotDefined": "bool", - "webGl2:shaderPrecisionFormats:blockIfNotDefined": "bool", - - "webGl:contextAttributes": "@WEBGL_CONTEXT_ATTRIBUTES", - "webGl2:contextAttributes": "@WEBGL_CONTEXT_ATTRIBUTES", - - "@WEBGL_PARAMS": { - "2849": "int", - "2884": "bool", - "2885": "int", - "2886": "int", - "2928": "array[int, 2]", - "2929": "bool", - "2930": "bool", - "2931": "int", - "2932": "int", - "2960": "bool", - "2961": "int", - "2962": "int", - "2963": "int", - "2964": "int", - "2965": "int", - "2966": "int", - "2967": "int", - "2968": "int", - "2978": "array[int, 4]", - "3024": "bool", - "3042": "bool", - "3074": "int | nil", - "3088": "array[int, 4]", - "3089": "bool", - "3106": "array[int, 4]", - "3107": "array[bool, 4]", - "3314": "int | nil", - "3315": "int | nil", - "3316": "int | nil", - "3317": "int", - "3330": "int | nil", - "3331": "int | nil", - "3332": "int | nil", - "3333": "int", - "3379": "int", - "3386": "array[int, 2]", - "3408": "int", - "3410": "int", - "3411": "int", - "3412": "int", - "3413": "int", - "3414": "int", - "3415": "int", - "7936": "str", - "7937": "str", - "7938": "str", - "10752": "int", - "32773": "array[int, 4]", - "32777": "int", - "32823": "bool", - "32824": "int", - "32873": "nil", - "32877": "int | nil", - "32878": "int | nil", - "32883": "int | nil", - "32926": "bool", - "32928": "bool", - "32936": "int", - "32937": "int", - "32938": "int", - "32939": "bool", - "32968": "int", - "32969": "int", - "32970": "int", - "32971": "int", - "33000": "int | nil", - "33001": "int | nil", - "33170": "int", - "33901": "array[double, 2]", - "33902": "array[double, 2]", - "34016": "int", - "34024": "int", - "34045": "int | nil", - "34047": "nil", - "34068": "nil", - "34076": "int", - "34467": "nil", - "34816": "int", - "34817": "int", - "34818": "int", - "34819": "int", - "34852": "int | nil", - "34853": "int | nil", - "34854": "int | nil", - "34855": "int | nil", - "34856": "int | nil", - "34857": "int | nil", - "34858": "int | nil", - "34859": "int | nil", - "34860": "int | nil", - "34877": "int", - "34921": "int", - "34930": "int", - "34964": "nil", - "34965": "nil", - "35071": "int | nil", - "35076": "int | nil", - "35077": "int | nil", - "35371": "int | nil", - "35373": "int | nil", - "35374": "int | nil", - "35375": "int | nil", - "35376": "int | nil", - "35377": "int | nil", - "35379": "int | nil", - "35380": "int | nil", - "35657": "int | nil", - "35658": "int | nil", - "35659": "int | nil", - "35660": "int", - "35661": "int", - "35723": "int | nil", - "35724": "str", - "35725": "nil", - "35738": "int", - "35739": "int", - "35968": "int | nil", - "35977": "bool | nil", - "35978": "int | nil", - "35979": "int | nil", - "36003": "int", - "36004": "int", - "36005": "int", - "36006": "nil", - "36007": "nil", - "36063": "int | nil", - "36183": "int | nil", - "36203": "int | nil", - "36345": "int | nil", - "36347": "int", - "36348": "int", - "36349": "int", - "36387": "bool | nil", - "36388": "bool | nil", - "36392": "nil", - "36795": "nil", - "37137": "int | double | nil", - "37154": "int | nil", - "37157": "int | nil", - "37440": "bool", - "37441": "bool", - "37443": "int", - "37444": "nil", - "37445": "str", - "37446": "str", - "37447": "int | nil", - "38449": "nil" - }, - - "@WEBGL_SHADER_PRECISION_FORMATS": { - "/^\\d+,\\d+$/": { - "*rangeMin": "int[>=0]", - "*rangeMax": "int[>=0]", - "*precision": "int[>=0]" - } - }, - - "@WEBGL_CONTEXT_ATTRIBUTES": { - "alpha": "bool", - "antialias": "bool", - "depth": "bool", - "failIfMajorPerformanceCaveat": "bool", - "powerPreference": "str[low, high, default]", - "premultipliedAlpha": "bool", - "preserveDrawingBuffer": "bool", - "stencil": "bool" - }, - - "canvas:aaOffset": "int", - "canvas:aaCapOffset": "bool", - - "voices": "array[@VOICE_TYPE]", - "voices:blockIfNotDefined": "bool", - "voices:fakeCompletion": "bool", - "voices:fakeCompletion:charsPerSecond": "double[>0]", - - "@VOICE_TYPE": { - "*isLocalService": "bool", - "*isDefault": "bool", - "*voiceURI": "str", - "*name": "str", - "*lang": "str" - }, - - "humanize": "bool", - "humanize:maxTime": "double[>=0]", - "humanize:minTime": "double[>=0]", - "showcursor": "bool", - - "allowMainWorld": "bool", - "forceScopeAccess": "bool", - "enableRemoteSubframes": "bool", - "disableTheming": "bool", - "memorysaver": "bool", - "addons": "array[str]", - "certificatePaths": "array[str]", - "certificates": "array[str]", - "debug": "bool" -}