Skip to content

Commit

Permalink
refactor(eventsub): use snake_case transform by default (#5916)
Browse files Browse the repository at this point in the history
  • Loading branch information
pajlada authored Feb 7, 2025
1 parent 9092f24 commit 02405b9
Show file tree
Hide file tree
Showing 16 changed files with 97 additions and 163 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
- Bugfix: Fixed the reply button showing for inline whispers and announcements. (#5863)
- Bugfix: Fixed suspicious user treatment update messages not being searchable. (#5865)
- Bugfix: Ensure miniaudio backend exits even if it doesn't exit cleanly. (#5896)
- Dev: Add initial experimental EventSub support. (#5837, #5895, #5897, #5904, #5910, #5903, #5915)
- Dev: Add initial experimental EventSub support. (#5837, #5895, #5897, #5904, #5910, #5903, #5915, #5916)
- Dev: Highlight checks now use non-capturing groups for the boundaries. (#5784)
- Dev: Removed unused PubSub whisper code. (#5898)
- Dev: Updated Conan dependencies. (#5776)
Expand Down
79 changes: 63 additions & 16 deletions lib/twitch-eventsub-ws/ast/lib/comment_commands.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,78 @@
from typing import List, Tuple
from typing import Optional

import logging
import re

CommentCommands = List[Tuple[str, str]]

log = logging.getLogger(__name__)


def parse_comment_commands(raw_comment: str) -> CommentCommands:
comment_commands: CommentCommands = []
class CommentCommands:
# Transform the key from whatever-case
# By default, all keys are transformed into snake_case
# e.g. `userID` transforms to `user_id`
name_transform: str = "snake_case"

# Whether the key should completely change its name
# If set, `name_transform` will do nothing
name_change: Optional[str] = None

# Don't fail when an optional object exists and its data is bad
dont_fail_on_deserialization: bool = False

# Deserialization hint, current use-cases can be replaced with
# https://github.com/Chatterino/chatterino2/issues/5912
tag: Optional[str] = None

inner_root: str = ""

def __init__(self, parent: Optional["CommentCommands"] = None) -> None:
if parent is not None:
self.name_transform = parent.name_transform
self.name_change = parent.name_change
self.dont_fail_on_deserialization = parent.dont_fail_on_deserialization
self.tag = parent.tag
self.inner_root = parent.inner_root

def parse(self, raw_comment: str) -> None:
def clean_comment_line(line: str) -> str:
return line.replace("/", "").replace("*", "").strip()

comment_lines = [line for line in map(clean_comment_line, raw_comment.splitlines()) if line != ""]

for comment in comment_lines:
parts = comment.split("=", 2)
if len(parts) != 2:
continue

def clean_comment_line(line: str) -> str:
return line.replace("/", "").replace("*", "").strip()
command = parts[0].strip()
value = parts[1].strip()

comment_lines = [line for line in map(clean_comment_line, raw_comment.splitlines()) if line != ""]
match command:
case "json_rename":
self.name_change = value
case "json_dont_fail_on_deserialization":
self.dont_fail_on_deserialization = bool(value.lower() == "true")
case "json_transform":
self.name_transform = value
case "json_inner":
self.inner_root = value
pass
case "json_tag":
self.tag = value
case other:
log.warning(f"Unknown comment command found: {other} with value {value}")

for comment in comment_lines:
parts = comment.split("=", 2)
if len(parts) != 2:
continue
def apply_name_transform(self, input_json_name: str) -> str:
if self.name_change is not None:
return self.name_change

command = parts[0].strip()
value = parts[1].strip()
comment_commands.append((command, value))
match self.name_transform:
case "snake_case":
return re.sub(r"(?<![A-Z])\B[A-Z]", r"_\g<0>", input_json_name).lower()

return comment_commands
case other:
log.warning(f"Unknown transformation '{other}', ignoring")
return input_json_name


def json_transform(input_str: str, transformation: str) -> str:
Expand Down
18 changes: 2 additions & 16 deletions lib/twitch-eventsub-ws/ast/lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(self, name: str, namespace: tuple[str, ...]) -> None:
self.name = name
self.constants: List[EnumConstant] = []
self.parent: str = ""
self.comment_commands: CommentCommands = []
self.comment_commands = CommentCommands()
self.inner_root: str = ""
self.namespace = namespace

Expand Down Expand Up @@ -47,18 +47,4 @@ def try_value_to_definition(self, env: Environment) -> str:
return env.get_template("enum-definition.tmpl").render(enum=self)

def apply_comment_commands(self, comment_commands: CommentCommands) -> None:
for command, value in comment_commands:
match command:
case "json_rename":
# Do nothing on enums
pass
case "json_dont_fail_on_deserialization":
# Do nothing on enums
pass
case "json_transform":
# Do nothing on enums
pass
case "json_inner":
self.inner_root = value
case other:
log.warning(f"Unknown comment command found: {other} with value {value}")
self.inner_root = comment_commands.inner_root
37 changes: 11 additions & 26 deletions lib/twitch-eventsub-ws/ast/lib/enum_constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import clang.cindex

from .comment_commands import CommentCommands, json_transform, parse_comment_commands
from .comment_commands import CommentCommands

log = logging.getLogger(__name__)

Expand All @@ -23,40 +23,25 @@ def __init__(
self.dont_fail_on_deserialization: bool = False

def apply_comment_commands(self, comment_commands: CommentCommands) -> None:
for command, value in comment_commands:
match command:
case "json_rename":
# Rename the key that this field will use in json terms
log.debug(f"Rename json key from {self.json_name} to {value}")
self.json_name = value
case "json_dont_fail_on_deserialization":
# Don't fail when an optional object exists and its data is bad
log.debug(f"Don't fail on deserialization for {self.name}")
self.dont_fail_on_deserialization = bool(value.lower() == "true")
case "json_transform":
# Transform the key from whatever-case to case specified by `value`
self.json_name = json_transform(self.json_name, value)
case "json_inner":
# Do nothing on members
pass
case "json_tag":
# Rename the key that this field will use in json terms
log.debug(f"Applied json tag on {self.json_name}: {value}")
self.tag = value
case other:
log.warning(f"Unknown comment command found: {other} with value {value}")
self.json_name = comment_commands.apply_name_transform(self.json_name)
self.tag = comment_commands.tag
self.dont_fail_on_deserialization = comment_commands.dont_fail_on_deserialization

@staticmethod
def from_node(node: clang.cindex.Cursor) -> EnumConstant:
def from_node(
node: clang.cindex.Cursor,
comment_commands: CommentCommands,
) -> EnumConstant:
assert node.type is not None

name = node.spelling

enum = EnumConstant(name)

if node.raw_comment is not None:
comment_commands = parse_comment_commands(node.raw_comment)
enum.apply_comment_commands(comment_commands)
comment_commands.parse(node.raw_comment)

enum.apply_comment_commands(comment_commands)

return enum

Expand Down
40 changes: 13 additions & 27 deletions lib/twitch-eventsub-ws/ast/lib/member.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from __future__ import annotations

from typing import Optional, List
from typing import Optional

import logging

import clang.cindex
from clang.cindex import CursorKind

from .comment_commands import CommentCommands, json_transform, parse_comment_commands
from .comment_commands import CommentCommands
from .membertype import MemberType

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -72,31 +72,16 @@ def __init__(
self.dont_fail_on_deserialization: bool = False

def apply_comment_commands(self, comment_commands: CommentCommands) -> None:
for command, value in comment_commands:
match command:
case "json_rename":
# Rename the key that this field will use in json terms
log.debug(f"Rename json key from {self.json_name} to {value}")
self.json_name = value
case "json_dont_fail_on_deserialization":
# Don't fail when an optional object exists and its data is bad
log.debug(f"Don't fail on deserialization for {self.name}")
self.dont_fail_on_deserialization = bool(value.lower() == "true")
case "json_transform":
# Transform the key from whatever-case to case specified by `value`
self.json_name = json_transform(self.json_name, value)
case "json_inner":
# Do nothing on members
pass
case "json_tag":
# Rename the key that this field will use in json terms
log.debug(f"Applied json tag on {self.json_name}: {value}")
self.tag = value
case other:
log.warning(f"Unknown comment command found: {other} with value {value}")
self.json_name = comment_commands.apply_name_transform(self.json_name)
self.tag = comment_commands.tag
self.dont_fail_on_deserialization = comment_commands.dont_fail_on_deserialization

@staticmethod
def from_field(node: clang.cindex.Cursor, namespace: tuple[str, ...]) -> Member:
def from_field(
node: clang.cindex.Cursor,
comment_commands: CommentCommands,
namespace: tuple[str, ...],
) -> Member:
assert node.type is not None

name = node.spelling
Expand Down Expand Up @@ -155,8 +140,9 @@ def from_field(node: clang.cindex.Cursor, namespace: tuple[str, ...]) -> Member:
member = Member(name, member_type, type_name, _is_trivially_copyable(node.type))

if node.raw_comment is not None:
comment_commands = parse_comment_commands(node.raw_comment)
member.apply_comment_commands(comment_commands)
comment_commands.parse(node.raw_comment)

member.apply_comment_commands(comment_commands)

return member

Expand Down
18 changes: 2 additions & 16 deletions lib/twitch-eventsub-ws/ast/lib/struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(self, name: str, namespace: tuple[str, ...]) -> None:
self.name = name
self.members: List[Member] = []
self.parent: str = ""
self.comment_commands: CommentCommands = []
self.comment_commands = CommentCommands()
self.inner_root: str = ""
self.namespace = namespace

Expand Down Expand Up @@ -47,18 +47,4 @@ def try_value_to_definition(self, env: Environment) -> str:
return env.get_template("struct-definition.tmpl").render(struct=self)

def apply_comment_commands(self, comment_commands: CommentCommands) -> None:
for command, value in comment_commands:
match command:
case "json_rename":
# Do nothing on structs
pass
case "json_dont_fail_on_deserialization":
# Do nothing on structs
pass
case "json_transform":
# Do nothing on structs
pass
case "json_inner":
self.inner_root = value
case other:
log.warning(f"Unknown comment command found: {other} with value {value}")
self.inner_root = comment_commands.inner_root
12 changes: 5 additions & 7 deletions lib/twitch-eventsub-ws/ast/lib/walker.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import clang.cindex
from clang.cindex import CursorKind

from .comment_commands import parse_comment_commands
from .comment_commands import CommentCommands
from .member import Member
from .enum_constant import EnumConstant
from .struct import Struct
Expand All @@ -28,7 +28,7 @@ def handle_node(self, node: clang.cindex.Cursor, struct: Optional[Struct], enum:
case CursorKind.STRUCT_DECL:
new_struct = Struct(node.spelling, self.namespace)
if node.raw_comment is not None:
new_struct.comment_commands = parse_comment_commands(node.raw_comment)
new_struct.comment_commands.parse(node.raw_comment)
new_struct.apply_comment_commands(new_struct.comment_commands)
if struct is not None:
new_struct.parent = struct.full_name
Expand All @@ -43,7 +43,7 @@ def handle_node(self, node: clang.cindex.Cursor, struct: Optional[Struct], enum:
case CursorKind.ENUM_DECL:
new_enum = Enum(node.spelling, self.namespace)
if node.raw_comment is not None:
new_enum.comment_commands = parse_comment_commands(node.raw_comment)
new_enum.comment_commands.parse(node.raw_comment)
new_enum.apply_comment_commands(new_enum.comment_commands)
if struct is not None:
new_enum.parent = struct.full_name
Expand All @@ -60,8 +60,7 @@ def handle_node(self, node: clang.cindex.Cursor, struct: Optional[Struct], enum:
# log.warning(
# f"enum constant decl {node.spelling} - enum comments: {enum.comment_commands} - node comments: {node.raw_comment}"
# )
constant = EnumConstant.from_node(node)
constant.apply_comment_commands(enum.comment_commands)
constant = EnumConstant.from_node(node, CommentCommands(enum.comment_commands))
enum.constants.append(constant)

case CursorKind.FIELD_DECL:
Expand All @@ -72,8 +71,7 @@ def handle_node(self, node: clang.cindex.Cursor, struct: Optional[Struct], enum:

# log.debug(f"{struct}: {type.spelling} {node.spelling} ({type.kind})")
if struct:
member = Member.from_field(node, self.namespace)
member.apply_comment_commands(struct.comment_commands)
member = Member.from_field(node, CommentCommands(struct.comment_commands), self.namespace)
struct.members.append(member)

case CursorKind.NAMESPACE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ namespace chatterino::eventsub::lib::messages {
}
*/

/// json_transform=snake_case
struct Metadata {
const std::string messageID;
const std::string messageType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ namespace chatterino::eventsub::lib::payload::channel_ban::v1 {
}
*/

/// json_transform=snake_case
struct Event {
// User ID (e.g. 117166826) of the user who's channel the event took place in
std::string broadcasterUserID;
Expand Down
Loading

0 comments on commit 02405b9

Please sign in to comment.