Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(eventsub): use snake_case transform by default #5916

Merged
merged 4 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading