Skip to content

Commit

Permalink
Split the CodeGenerator class
Browse files Browse the repository at this point in the history
  • Loading branch information
mnogu committed Jun 4, 2023
1 parent 1319f3e commit 050e113
Show file tree
Hide file tree
Showing 17 changed files with 920 additions and 604 deletions.
1 change: 0 additions & 1 deletion chitose/app/bsky/feed/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from .get_posts import _get_posts
from .get_reposted_by import _get_reposted_by
from .get_timeline import _get_timeline
import chitose.app.bsky.actor.defs
import typing

class Feed_:
Expand Down
2 changes: 0 additions & 2 deletions chitose/app/bsky/notification/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
from .get_unread_count import _get_unread_count
from .list_notifications import _list_notifications
from .update_seen import _update_seen
import chitose.app.bsky.actor.defs
import chitose.com.atproto.label.defs
import typing

class Notification_:
Expand Down
2 changes: 1 addition & 1 deletion chitose/com/atproto/label/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from chitose.xrpc import XrpcSubscribe
from .query_labels import _query_labels
from .subscribe_labels import _subscribe_labels
import chitose.com.atproto.label.defs
import chitose
import typing

class Label_:
Expand Down
2 changes: 1 addition & 1 deletion chitose/com/atproto/sync/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from .notify_of_update import _notify_of_update
from .request_crawl import _request_crawl
from .subscribe_repos import _subscribe_repos
import chitose.com.atproto.sync.subscribe_repos
import chitose
import typing

class Sync_:
Expand Down
23 changes: 23 additions & 0 deletions codegen/array.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import ast

from codegen.common import Generator
from codegen.common import to_class_name
from codegen.lexicon import Lexicon


class ArrayGenerator(Generator):
def __init__(self, lexicon: Lexicon) -> None:
self.lexicon = lexicon

def generate(self) -> ast.Assign:
return ast.Assign(
targets=[
ast.Name(
id=to_class_name(self.lexicon.get_def_id()),
ctx=ast.Store()
)
],
value=self.lexicon.get_array_annotation(
self.lexicon.current, True
)
)
63 changes: 63 additions & 0 deletions codegen/call.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import ast

from typing import Any
from typing import Union

from codegen.common import Generator
from codegen.common import to_description
from codegen.function import FunctionGenerator
from codegen.lexicon import Lexicon


class CallGenerator(Generator):
# for query and procedure
def __init__(self, lexicon: Lexicon,
properties: dict[str, Any], required: list[str],
query_params: ast.List,
data: Union[ast.Dict, ast.Constant, ast.Name],
headers: ast.Dict) -> None:
self.lexicon = lexicon
self.query_params = query_params
self.data = data
self.headers = headers

self.generator = FunctionGenerator(lexicon, properties, required)

def generate(self) -> ast.FunctionDef:
return self.generator.generate(
self._args(), self._body(), ast.Name(id='bytes', ctx=ast.Load())
)

def _args(self) -> ast.arguments:
self.lexicon.add_module('chitose')
args = [
self.generator.get_func_arg('call', 'XrpcCall')
] + self.generator.get_property_args()

self.lexicon.append_function(
self.generator.get_info(args, 'call')
)

return self.generator.get_args(args)

def _body(self) -> list[Union[ast.Expr, ast.Return]]:
description_lines = self.generator.get_description_lines()
return [
ast.Expr(
value=ast.Constant(
value=to_description(description_lines, 4)
)
),
ast.Return(
value=ast.Call(
func=ast.Name(id='call', ctx=ast.Load()),
args=[
ast.Constant(value=self.lexicon.get_id()),
self.query_params,
self.data,
self. headers,
],
keywords=[]
)
)
]
120 changes: 120 additions & 0 deletions codegen/function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import ast

from typing import Any
from typing import Union

from codegen.common import to_private_function_name
from codegen.common import to_snake
from codegen.common import FunctionInfo
from codegen.lexicon import Lexicon


class FunctionGenerator:
# for query, procedure and subscription
def __init__(self, lexicon: Lexicon,
properties: dict[str, Any], required: list[str]) -> None:
self.lexicon = lexicon
self.properties = properties
self.required = required

def generate(self, args: ast.arguments,
body: list[Union[ast.Expr, ast.Return]],
returns: Union[ast.Constant, ast.Name]) -> ast.FunctionDef:
return ast.FunctionDef(
name=to_private_function_name(self.lexicon.get_name()),
args=args,
body=body,
decorator_list=[],
returns=returns,
)

@staticmethod
def get_query_properties_and_required(lexicon: Lexicon) \
-> tuple[dict[str, Any], list[str]]:
if 'parameters' not in lexicon.current:
return {}, []

parameters = lexicon.current['parameters']
assert parameters['type'] == 'params'

properties = parameters['properties']
required = parameters.get('required', [])

properties = lexicon.reorder_properties(properties, required)
return properties, required

@staticmethod
def get_query_params(properties: dict[str, Any]) -> ast.List:
return ast.List(
elts=[
ast.Tuple(
elts=[
ast.Constant(value=property),
ast.Name(id=to_snake(property), ctx=ast.Load())
],
ctx=ast.Load()
)
for property in properties
]
)

def get_func_arg(self, func: str, attr: str) -> ast.arg:
return ast.arg(
arg=func,
annotation=ast.Attribute(
value=ast.Attribute(
value=ast.Name(id='chitose', ctx=ast.Load()),
attr='xrpc',
ctx=ast.Load()
),
attr=attr,
ctx=ast.Load()
),
)

def get_property_args(self) -> list[ast.arg]:
return [
ast.arg(
arg=to_snake(property),
annotation=self.lexicon.get_annotation(
self.properties, self.required, property)
)
for property in self.properties
]

def _none_count(self):
return len(self.properties) - len(self.required)

def get_args(self, args: list[ast.arg]) -> ast.arguments:
none_count = self._none_count()
return ast.arguments(
posonlyargs=[],
args=args,
kwonlyargs=[],
kw_defaults=[],
defaults=[
ast.Constant(value=None)
for _ in range(none_count)
]
)

def get_description_lines(self) -> list[str]:
lines = [self.lexicon.current.get('description', '')]
for property, detail in self.properties.items():
if 'description' not in detail:
continue

params = f':param {to_snake(property)}: {detail["description"]}'
lines.append(params)
return lines

def get_info(self, args: list[ast.arg], func: str) -> FunctionInfo:
none_count = self._none_count()
return FunctionInfo(
name=to_snake(self.lexicon.get_name()),
description_lines=self.get_description_lines(),
args=args[1:],
none_count=none_count,
modules=self.lexicon.get_annotation_modules(),
func=func,
)
143 changes: 143 additions & 0 deletions codegen/klass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import ast

from typing import Any

from codegen.common import Generator
from codegen.common import to_class_name
from codegen.common import to_description
from codegen.common import to_snake
from codegen.lexicon import Lexicon


class ClassGenerator(Generator):
# for object and record
def __init__(self, lexicon: Lexicon,
properties: dict[str, Any], required: list[str]) -> None:
self.lexicon = lexicon
self.properties = properties
self.required = required

self.def_id = lexicon.get_def_id()
self.current = lexicon.get_current()

def generate(self) -> ast.ClassDef:
name = self.lexicon.get_name()
if self.def_id != 'main':
if self.def_id == name:
# ex.
# id: spam.ham.eggs
# name: eggs
# def_id: eggs
# => name: EggsEggs
name = to_class_name(name) * 2
else:
name = self.def_id

name = to_class_name(name)
base_name = to_class_name(self.current['type'])
self.lexicon.add_module('chitose')
return ast.ClassDef(
name=name,
bases=[
ast.Name(id=f'chitose.{base_name}', ctx=ast.Load()),
],
keywords=[],
body=[
self._class_comment(self.properties),
self._init_function(self.properties, self.required),
self._to_dict_function(self.properties)
],
decorator_list=[]
)

def _class_comment(self, properties: dict[str, Any]) -> ast.Expr:
lines = ['']
for property in properties:
description = properties[property].get('description')
if description:
lines.append(f':param {to_snake(property)}: {description}')

return ast.Expr(value=ast.Constant(value=to_description(lines, 4)))

def _init_function(self, properties: dict[str, Any],
required: list[str]) -> ast.FunctionDef:
args = [
ast.arg(arg='self')
]
args += [
ast.arg(
arg=to_snake(property),
annotation=self.lexicon.get_annotation(
properties, required, property
)
)
for property in properties
]
return ast.FunctionDef(
name='__init__',
args=ast.arguments(
posonlyargs=[],
args=args,
kwonlyargs=[],
kw_defaults=[],
defaults=[
ast.Constant(value=None)
for _ in range(len(properties) - len(required))
]),
body=[
ast.Assign(
targets=[
ast.Attribute(
value=ast.Name(id='self', ctx=ast.Load()),
attr=to_snake(property),
ctx=ast.Store())
],
value=ast.Name(id=to_snake(property), ctx=ast.Load())
)
for property in properties
],
decorator_list=[],
returns=ast.Constant(value=None))

def _get_ref(self):
ref = self.lexicon.get_id()
if self.def_id != 'main':
ref += f'#{self.def_id}'
return ref

def _to_dict_function(self,
properties: dict[str, Any]) -> ast.FunctionDef:
return ast.FunctionDef(
name='to_dict',
args=ast.arguments(
posonlyargs=[],
args=[
ast.arg(arg='self')],
kwonlyargs=[],
kw_defaults=[],
defaults=[]),
body=[
ast.Return(
value=ast.Dict(
keys=[
ast.Constant(value=property)
for property in properties
] + [
ast.Constant(value='$type')
],
values=[
ast.Attribute(
value=ast.Name(id='self', ctx=ast.Load()),
attr=to_snake(property),
ctx=ast.Load())
for property in properties
] + [
ast.Constant(value=self._get_ref(), ctx=ast.Load())
]

)
)
],
decorator_list=[],
returns=ast.Name(id='dict', ctx=ast.Load())
)
Loading

0 comments on commit 050e113

Please sign in to comment.