build(python): simplify rpycdec vendor layout

This commit was merged in pull request #17.
This commit is contained in:
2026-09-06 17:22:16 -04:00
parent 884789d676
commit aab32198d1
56 changed files with 10 additions and 19 deletions

View File

@@ -0,0 +1 @@
# Ren'Py AST and support modules for rpycdec

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
class PyExpr(str):
expr: str
filename: str
linenumber: int
py: int
hashcode: str
column: int
def __new__(
cls,
expr: str,
filename: str = '',
linenumber: int = 0,
py: int = 3,
hashcode: str = '',
column: int = 0,
):
self = super().__new__(cls, expr)
self.expr = expr
self.filename = filename
self.linenumber = linenumber
self.py = py
self.hashcode = hashcode
self.column = column
return self
def get_code(self, **kwargs) -> str:
return self.expr
def __str__(self):
return self.expr

View File

@@ -0,0 +1,319 @@
from . import util, ast, astsupport
from .object import Object
class Context(object):
pass
class ATLTransformBase(Object):
pass
class RawStatement(object):
pass
class Statement(Object):
pass
class RawBlock(RawStatement):
statements = None
"""
https://www.renpy.org/doc/html/transforms.html#block-statement
atl_block_stmt ::= "block" ":"
atl_block
"""
def get_code(self, **kwargs) -> str:
if (
not self.animation
and len(self.statements) == 1
and isinstance(self.statements[0], RawBlock)
):
return util.get_code(self.statements[0], **kwargs)
rv = []
if self.animation:
"""
https://www.renpy.org/doc/html/transforms.html#animation-statement
atl_animation ::= "animation"
image eileen vhappy moving:
animation
"eileen vhappy"
xalign 0.0
linear 5.0 xalign 1.0
repeat
"""
rv.append("animation")
statements = self.statements
if statements:
for statement in statements:
if statement is None:
rv.append("pass")
else:
rv.append(util.get_code(statement, **kwargs))
else:
rv.append("pass")
return "\n".join(rv)
class Block(Statement):
pass
class RawMultipurpose(RawStatement):
"""
https://www.renpy.org/doc/html/transforms.html#interpolation-statement
atl_interp ::= ((warper simple_expression) | ("warp" simple_expression simple_expression)) (atl_interp_target+ | (":"
atl_interp_target+ ))
atl_interp_target ::= (atl_property+ ("knot" simple_expression)* )
| atl_transform_expression
| "clockwise"
| "counterclockwise"
| ("circles" simple_expression)
"""
def get_code(self, **kwargs) -> str:
start = ""
warp_fun, duration, wrap = self.warp_function, self.duration, self.warper
if warp_fun and wrap is None:
# warp {warp_function:expr} {duration:expr}
start = f"warp {util.get_code(warp_fun, **kwargs)} {util.get_code(duration, **kwargs)}"
elif not warp_fun and wrap:
# {wrap} {duration:expr}
start = f"{wrap} {util.get_code(duration, **kwargs)}"
elif not warp_fun and not wrap and duration == "0":
pass
rv = []
revolution = self.revolution
if revolution:
rv.append(revolution)
circles = self.circles
if circles and circles != "0":
# circles {expression}
rv.append(f"circles {util.get_code(circles, **kwargs)}")
for prop, expr in self.properties:
rv.append(f"{prop} {util.get_code(expr, **kwargs)}")
for prop, exprs in self.splines:
last, exprs = exprs[-1], exprs[:-1]
knots = " ".join(f"knot {util.get_code(expr, **kwargs)}" for expr in exprs)
rv.append(f"{prop} {util.get_code(last, **kwargs)} {knots}")
for expr, with_expr in self.expressions:
if with_expr is None:
rv.append(str(expr))
continue
if isinstance(with_expr, ast.PyExpr):
valstr = f"with {with_expr}"
elif isinstance(with_expr, astsupport.PyExpr):
valstr = f"with {with_expr}"
else:
valstr = str(with_expr)
rv.append(f"{expr} {valstr}")
if not start:
return " ".join(rv)
if not rv:
# if no properties, return just the warp
return start
if len(rv) < 3:
# if only few properties, return them in one line
return start + " " + " ".join(rv)
return start + ":\n" + util.indent(" ".join(rv))
class RawContainsExpr(RawStatement):
"""
https://www.renpy.org/doc/html/transforms.html#inline-contains-statement
atl_contains ::= "contains" simple_expression
"""
def get_code(self, **kwargs) -> str:
"""
Returns the code for a contains expression.
"""
expression = util.attr(self, "expression")
if expression:
return f"contains {util.get_code(expression, **kwargs)}"
return "contains"
# This allows us to have multiple ATL transforms as children.
class RawChild(RawStatement):
"""
https://www.renpy.org/doc/html/transforms.html#contains-block-statement
atl_counts ::= "contains" ":"
atl_block
"""
def get_code(self, **kwargs) -> str:
children = util.attr(self, "children")
if not children:
return "contains:\n pass"
rv = ["contains:"]
rv.append(util.indent(util.get_code(children, **kwargs)))
return "\n".join(rv)
# This changes the child of this statement, optionally with a transition.
class Child(Statement):
pass
# This causes interpolation to occur.
class Interpolation(Statement):
pass
class RawRepeat(RawStatement):
"""
https://www.renpy.org/doc/html/transforms.html#repeat-statement
atl_repeat ::= "repeat" (simple_expression)?
"""
def get_code(self, **kwargs) -> str:
if self.repeats:
return f"repeat {util.get_code(self.repeats, **kwargs)}"
return "repeat"
class Repeat(Statement):
pass
class RawParallel(RawStatement):
"""
https://www.renpy.org/doc/html/transforms.html#parallel-statement
atl_parallel ::= ("parallel" ":"
atl_block)+
"""
def get_code(self, **kwargs) -> str:
rv = ["parallel:"]
blocks = util.attr(self, "blocks")
if not blocks:
rv.append(util.indent("pass"))
else:
rv.append(util.indent(util.get_code(blocks, **kwargs)))
return "\n".join(rv)
class Parallel(Statement):
pass
class RawChoice(RawStatement):
"""
https://www.renpy.org/doc/html/transforms.html#choice-statement
atl_choice ::= ("choice" (simple_expression)? ":"
atl_block)+
"""
def get_code(self, **kwargs) -> str:
rv = []
for text, stmt in self.choices:
rv.append(f"choice {text}:")
rv.append(util.indent(util.get_code(stmt, **kwargs)))
return "\n".join(rv)
class Choice(Statement):
pass
class RawTime(RawStatement):
"""
https://www.renpy.org/doc/html/atl.html#time-statement
atl_time ::= "time" (simple_expression)?
"""
def get_code(self, **kwargs) -> str:
time = util.attr(self, "time")
if time:
return f"time {util.get_code(time, **kwargs)}"
return "time"
class Time(Statement):
pass
class RawOn(RawStatement):
"""
https://www.renpy.org/doc/html/transforms.html#on-statement
atl_on ::= "on" name [ "," name ] * ":"
atl_block
on show:
alpha 0.0
linear .5 alpha 1.0
on hide:
linear .5 alpha 0.0
"""
def get_code(self, **kwargs) -> str:
rv = []
for text, stmt in self.handlers.items():
rv.append(f"on {text}:")
rv.append(util.indent(util.get_code(stmt, **kwargs)))
return "\n".join(rv)
class On(Statement):
pass
class RawEvent(RawStatement):
"""
https://www.renpy.org/doc/html/transforms.html#event-statement
atl_event ::= "event" name
"""
def get_code(self, **kwargs) -> str:
name = util.attr(self, "name")
if name:
return f"event {name}"
return "event"
class Event(Statement):
pass
class RawFunction(RawStatement):
"""
https://www.renpy.org/doc/html/transforms.html#function-statement
atl_function ::= "function" simple_expression
"""
def get_code(self, **kwargs) -> str:
if self.expr:
return "function " + str(self.expr)
else:
return "function"
class Function(Statement):
pass

View File

@@ -0,0 +1,15 @@
from typing import Optional
# Do we use old text substitutions?
old_substitutions = True
# An alternate path to use when uneliding. (Mostly used by the launcher to enable
# the style inspector.)
alternate_unelide_path = None
# Various directories.
gamedir = ""
basedir = ""
renpy_base = ""
commondir = None # type: Optional[str]
logdir = None # type: Optional[str] # Where log and error files go.

View File

@@ -0,0 +1,71 @@
from . import layout, core
from ..text import text
from .. import object as renpyobject, python
class Keymap(layout.Null):
pass
class RollForward(layout.Null):
pass
class PauseBehavior(layout.Null):
pass
class DismissBehavior(layout.Null):
pass
class SoundStopBehavior(layout.Null):
pass
class SayBehavior(layout.Null):
pass
class Button(layout.Window):
pass
class ImageButton(Button):
pass
class HoveredProxy(object):
pass
class Input(text.Text): # @UndefinedVariable
pass
class Adjustment(renpyobject.Object):
pass
class Bar(core.Displayable):
pass
class Conditional(layout.Container):
pass
class TimerState(python.RevertableObject):
pass
class Timer(layout.Null):
pass
class MouseArea(core.Displayable):
pass
class OnEvent(core.Displayable):
pass

View File

@@ -0,0 +1,33 @@
from .. import object as renpyobject
class IgnoreEvent(Exception):
pass
class EndInteraction(Exception):
pass
class absolute(float):
pass
class DisplayableArguments(renpyobject.Object):
pass
class Displayable(renpyobject.Object):
pass
class SceneLists(renpyobject.Object):
pass
class MouseMove(object):
pass
class Interface(object):
pass

View File

@@ -0,0 +1,10 @@
from . import core, layout
from .. import python
class Drag(core.Displayable, python.RevertableObject):
pass
class DragGroup(layout.MultiBox):
pass

View File

@@ -0,0 +1,84 @@
from ..display import core, layout
from .. import object as renpyobject
class Null(core.Displayable):
pass
class Container(core.Displayable):
pass
class Position(layout.Container):
pass
class Grid(Container):
pass
class IgnoreLayers(Exception):
pass
class MultiBox(layout.Container):
pass
class SizeGroup(renpyobject.Object):
pass
class Window(Container):
pass
class DynamicDisplayable(core.Displayable):
pass
def ConditionSwitch(*args, **kwargs):
pass
class IgnoresEvents(Container):
pass
def Crop(rect, child, **properties):
pass
LiveCrop = Crop
class Side(Container):
pass
class Alpha(core.Displayable):
pass
class AdjustTimes(Container):
pass
class Tile(Container):
pass
LiveTile = Tile
class Flatten(Container):
pass
class AlphaMask(Container):
pass
class NearRect(Container):
pass

View File

@@ -0,0 +1,2 @@
class Transform:
pass

View File

@@ -0,0 +1,50 @@
import os
from typing import NamedTuple
import renpy
class SubParse(object):
"""
This represents the information about a subparse that can be provided to
a creator-defined statement.
"""
def __init__(self, block):
self.block = block
def __repr__(self):
if not self.block:
return "<SubParse empty>"
else:
return "<SubParse {}:{}>".format(
self.block[0].filename, self.block[0].linenumber
)
def unelide_filename(fn: str) -> str:
fn = os.path.normpath(fn)
if renpy.config.alternate_unelide_path is not None:
fn0 = os.path.join(renpy.config.alternate_unelide_path, fn)
if os.path.exists(fn0):
return fn0
fn1 = os.path.join(renpy.config.basedir, fn)
if os.path.exists(fn1):
return fn1
fn2 = os.path.join(renpy.config.renpy_base, fn)
if os.path.exists(fn2):
return fn2
return fn
class GroupedLine(NamedTuple):
# The filename the line is from.
filename: str
number: int
indent: int
text: str
block: list["GroupedLine"]

View File

@@ -0,0 +1,33 @@
class Object(object):
__version__ = 0
nosave = []
def __getstate__(self):
rv = vars(self).copy()
for f in self.nosave:
if f in rv:
del rv[f]
rv["__version__"] = self.__version__
return rv
# None, to prevent this from being called when unnecessary.
after_setstate = None
def __setstate__(self, new_dict):
version = new_dict.pop("__version__", 0)
self.__dict__.update(new_dict)
if version != self.__version__:
self.after_upgrade(version) # E1101
if self.after_setstate:
self.after_setstate() # E1102
class Sentinel(object):
pass

View File

@@ -0,0 +1,63 @@
from .. import util
class Signature:
def get_code(self, **kwargs) -> str:
rv = []
parameters = util.attr(self, "parameters")
if isinstance(parameters, dict):
for name, value in parameters.items():
rv.append(util.get_code(value, **kwargs))
else:
for value in parameters:
rv.append(str(value))
if not rv:
return ""
return f"({', '.join(rv)})"
class Parameter:
(
POSITIONAL_ONLY,
POSITIONAL_OR_KEYWORD,
VAR_POSITIONAL,
KEYWORD_ONLY,
VAR_KEYWORD,
) = range(5)
def _variable_or_default(self, name, default):
return f"{name}={default}" if default is not None else f"{name}"
def get_code(self, **kwargs) -> str:
name = util.attr(self, "name")
kind = util.attr(self, "kind")
default = util.attr(self, "default")
if kind == self.POSITIONAL_ONLY:
return self._variable_or_default(name, default)
elif kind == self.POSITIONAL_OR_KEYWORD:
return self._variable_or_default(name, default)
elif kind == self.VAR_POSITIONAL:
return f"*{name}"
elif kind == self.KEYWORD_ONLY:
return self._variable_or_default(name, default)
elif kind == self.VAR_KEYWORD:
return f"**{name}"
else:
raise ValueError(f"Unknown parameter kind: {kind}")
class ArgumentInfo:
def get_code(self, **kwargs) -> str:
rv = []
for i, (keyword, expression) in enumerate(self.arguments):
if i in self.starred_indexes:
rv.append("*" + expression)
elif i in self.doublestarred_indexes:
rv.append("**" + expression)
elif keyword is not None:
rv.append("{}={}".format(keyword, expression))
else:
rv.append(str(expression))
return "(" + ", ".join(rv) + ")"

View File

@@ -0,0 +1,25 @@
import ast
# Three levels of constness.
GLOBAL_CONST = 2 # Expressions that are const everywhere.
LOCAL_CONST = 1 # Expressions that are const with regard to a screen + parameters.
NOT_CONST = 0 # Expressions that are not const.
class Control(object):
pass
class Analysis(object):
pass
class PyAnalysis(ast.NodeVisitor):
pass
class CompilerCache(object):
pass
ccache = CompilerCache()

View File

@@ -0,0 +1,82 @@
import ast
import random
from .object import Object
class StoreDeleted(object):
pass
class StoreModule(object):
pass
class StoreDict(dict):
pass
class StoreBackup:
pass
class NoRollback(object):
pass
class WrapNode(ast.NodeTransformer):
pass
class CompressedList(object):
pass
class RevertableList(list):
pass
class RevertableDict(dict):
pass
class RevertableSet(set):
def __setstate__(self, state):
if isinstance(state, tuple):
self.update(state[0].keys())
else:
self.update(state)
__reduce__ = object.__reduce__
__reduce_ex__ = object.__reduce_ex__
class RevertableObject(object):
pass
class RollbackRandom(random.Random):
pass
class DetRandom(random.Random):
pass
class Rollback(Object):
pass
class RollbackLog(Object):
pass
class StoreProxy(object):
pass
def py_compile(source, mode, filename="<none>", lineno=1, ast_node=False, cache=True):
pass
def py_eval_bytecode(bytecode, globals=None, locals=None): # @ReservedAssignment
pass

View File

@@ -0,0 +1,83 @@
import ast
import random
from .object import Object
class StoreDeleted(object):
pass
class StoreModule(object):
pass
class StoreDict(dict):
pass
class StoreBackup:
pass
class NoRollback(object):
pass
class WrapNode(ast.NodeTransformer):
pass
class CompressedList(object):
pass
class RevertableList(list):
pass
class RevertableDict(dict):
pass
class RevertableSet(set):
def __setstate__(self, state):
if isinstance(state, tuple):
self.update(state[0].keys())
else:
self.update(state)
# Required to ensure that getstate and setstate are called.
__reduce__ = object.__reduce__
__reduce_ex__ = object.__reduce_ex__
class RevertableObject(object):
pass
class RollbackRandom(random.Random):
pass
class DetRandom(random.Random):
pass
class Rollback(Object):
pass
class RollbackLog(Object):
pass
class StoreProxy(object):
pass
def py_compile(source, mode, filename="<none>", lineno=1, ast_node=False, cache=True):
pass
def py_eval_bytecode(bytecode, globals=None, locals=None): # @ReservedAssignment
pass

View File

@@ -0,0 +1,12 @@
def collapse_stmts(stmts):
"""
Returns a flat list containing every statement in the tree
stmts.
"""
rv = []
for i in stmts:
i.get_children(rv.append)
return rv

View File

@@ -0,0 +1,323 @@
import logging
from .. import ast, astsupport, ui, util
class SLContext(ui.Addable):
pass
class SLNode(object):
pass
class SLBlock(SLNode):
"""
foo bar
baz 1
expression
"""
def get_code(self, **kwargs) -> str:
rv = []
if self.keyword:
rv.append(util.get_code_properties(self.keyword, newline=True))
if self.children:
rv.append(util.get_code(self.children, **kwargs))
return "\n".join(rv)
class SLCache(object):
pass
class SLDisplayable(SLBlock):
"""
`displayable`
A function that, when called with the positional and keyword
arguments, causes the displayable to be displayed.
`scope`
If true, the scope is supplied as an argument to the displayable.
`child_or_fixed`
If true and the number of children of this displayable is not one,
the children are added to a Fixed, and the Fixed is added to the
displayable.
`style`
The base name of the main style.
`pass_context`
If given, the context is passed in as the first positonal argument
of the displayable.
`imagemap`
True if this is an imagemap, and should be handled as one.
`hotspot`
True if this is a hotspot that depends on the imagemap it was
first displayed with.
`replaces`
True if the object this displayable replaces should be
passed to it.
`default_keywords`
The default keyword arguments to supply to the displayable.
`variable`
A variable that the main displayable is assigned to.
https://www.renpy.org/doc/html/screens.html#add-statement
https://www.renpy.org/doc/html/screens.html#use
https://www.renpy.org/doc/html/screens.html#has
https://www.renpy.org/doc/html/screens.html#imagebutton
"""
def get_name(self) -> str:
# higher version use style instead of name
name = getattr(self, "name", None)
displayable = getattr(self, "displayable", None)
style = getattr(self, "style", None)
start = ""
if name:
start = name
elif displayable:
# displayable function named sl2xxx like sl2vbar , sl2viewport
displable_name = displayable.__name__.lower()
if displable_name.startswith("sl2"):
start = displable_name.replace("sl2", "")
elif displable_name == "onevent":
start = "on"
else:
start = displable_name.replace("_", "")
if style:
match start:
case "multibox":
start = style
case "window":
start = style
if not start:
logging.warning(
"warning: displayable not found, use textbutton instead, you may need fix it manually"
)
start = "textbutton"
return start
def get_code(self, **kwargs) -> str:
# higher version use style instead of name
start = self.get_name()
if not start:
raise Exception("displayable not found")
variable = util.attr(self, "variable")
if variable:
start += f" as {variable}"
tag = util.attr(self, "tag")
if tag:
start += f" tag {tag}"
positional = util.attr(self, "positional")
if positional:
if isinstance(positional, list):
start += " " + " ".join(util.get_code(p, **kwargs) for p in positional)
else:
start += " " + util.get_code(positional, **kwargs)
# keywords
keyword = util.attr(self, "keyword")
children = util.attr(self, "children")
if not children:
if keyword:
start += f" {util.get_code_properties(keyword)}"
return start
# children
rv = [start + ":"]
if keyword:
rv.append(util.indent(util.get_code_properties(keyword, newline=True)))
rv.append(util.indent(util.get_code(children, **kwargs)))
return "\n".join(rv)
class SLIf(SLNode):
def get_code(self, **kwargs) -> str:
rv = []
for index, (cond, body) in enumerate(self.entries):
body_code = util.get_code(body, **kwargs)
if not body_code:
body_code = "pass"
body_code = util.indent(body_code)
if index == 0:
rv.append(f"if {cond}:\n{body_code}")
continue
if cond and cond != "True":
rv.append(f"elif {cond}:\n{body_code}")
else:
rv.append(f"else:\n{body_code}")
break
return "\n".join(rv)
class SLShowIf(SLNode):
"""
https://www.renpy.org/doc/html/screens.html#showif-statement
showif n == 3:
text "Three" size 100 at cd_transform
elif n == 2:
text "Two" size 100 at cd_transform
elif n == 1:
text "One" size 100 at cd_transform
else:
text "Liftoff!" size 100 at cd_transform
"""
def get_code(self, **kwargs) -> str:
rv = []
for index, (cond, body) in enumerate(self.entries):
body_code = util.get_code(body, **kwargs)
if not body_code:
body_code = "pass"
body_code = util.indent(body_code)
if index == 0:
rv.append(f"showif {cond}:\n{body_code}")
continue
if cond and cond != "True":
rv.append(f"elif {cond}:\n{body_code}")
else:
rv.append(f"else:\n{body_code}")
break
return "\n".join(rv)
class SLFor(SLBlock):
pass
"""
screen five_buttons():
vbox:
for i, numeral index numeral in enumerate(numerals):
textbutton numeral action Return(i + 1)
"""
def get_code(self, **kwargs) -> str:
rv = [f"for {self.variable} in {self.expression}:"]
children = util.attr(self, "children")
if not children:
rv.append(util.indent("pass"))
else:
rv.append(util.indent(util.get_code(children, **kwargs)))
return "\n".join(rv)
class SLPython(SLNode):
def get_code(self, **kwargs) -> str:
inner_code = util.get_code(self.code, **kwargs)
if not inner_code:
return ""
if len(inner_code.splitlines()) == 1:
return f"$ {inner_code}"
return f"python:\n{util.indent(inner_code)}"
class SLPass(SLNode):
pass
class SLDefault(SLNode):
"""
https://www.renpy.org/doc/html/python.html#default-statement
"""
def get_code(self, **kwargs) -> str:
return f"default {self.variable} = {self.expression}"
class SLUse(SLNode):
"""
https://www.renpy.org/doc/html/screens.html#use
use [expression EXPR pass|WORD] ARGUMENTS [id EXPR][:]
BLOCK
"""
def get_code(self, **kwargs) -> str:
start = "use"
target = util.attr(self, "target")
if target:
if isinstance(target, ast.PyExpr):
start += f" expression {util.get_code(target, **kwargs)} pass "
elif isinstance(target, astsupport.PyExpr):
start += f" expression {util.get_code(target, **kwargs)} pass "
else:
start += f" {target}"
if self.args:
start += f"{util.get_code(self.args, **kwargs)}"
if self.block:
return util.label_code(start, self.block, **kwargs)
if self.ast:
return util.label_code(start, self.ast, **kwargs)
return start
# https://www.renpy.org/doc/html/screens.html#use-and-transclude
"""
screen movable_frame(pos):
drag:
pos pos
frame:
background Frame("movable_frame.png", 10, 10)
top_padding 20
transclude
"""
class SLTransclude(SLNode):
def get_code(self, **kwargs) -> str:
return "transclude"
class SLScreen(SLBlock):
"""
https://www.renpy.org/doc/html/screens.html#screen-statement
"""
parameters: ast.ParameterInfo
keyword: list
def get_code(self, **kwargs) -> str:
start = "screen"
if self.name:
start += f" {self.name}"
if self.parameters:
start += f"{util.get_code(self.parameters, **kwargs)}"
properties = self.keyword.copy()
tag = util.attr(self, "tag")
if tag:
properties.append(("tag", tag))
rv = [start + ":"]
# keyword
if properties:
rv.append(util.indent(util.get_code_properties(properties, newline=True)))
rv.append("") # add a newline after keyword
# children
children = util.attr(self, "children")
if children:
rv.append(util.indent(util.get_code(children, **kwargs)))
if len(rv) == 1:
rv.append("pass")
return "\n".join(rv)
class ScreenCache(object):
pass
class SLContinue(SLNode):
def get_code(self, **kwargs) -> str:
return "continue"

View File

@@ -0,0 +1,25 @@
from ..display import layout
class ShowIf(layout.Container):
pass
def sl2vbar(context=None, **properties):
pass
def sl2viewport(context=None, **kwargs):
pass
def sl2vpgrid(context=None, **kwargs):
pass
def sl2add(d, replaces=None, scope=None, **kwargs):
pass
def sl2bar(context=None, **properties):
pass

View File

View File

@@ -0,0 +1,2 @@
class ParameterizedText(object):
pass

View File

@@ -0,0 +1,26 @@
class ImageFont(object):
pass
class FontGroup(object):
pass
class ScaledImageFont(ImageFont):
pass
class BMFont(ImageFont):
pass
class MudgeFont(ImageFont):
pass
class SFont(ImageFont):
pass
class ImageFont(object):
pass

View File

@@ -0,0 +1,33 @@
from ..display import core
class Blit(object):
pass
class DrawInfo(object):
pass
class TextSegment(object):
pass
class SpaceSegment(object):
pass
class DisplayableSegment(object):
pass
class FlagSegment(object):
pass
class Layout(object):
pass
class Text(core.Displayable):
pass

View File

@@ -0,0 +1,28 @@
import re
def encode_say_string(s):
"""
Encodes a string in the format used by Ren'Py say statements.
"""
s = s.replace("\\", "\\\\")
s = s.replace("\n", "\\n")
s = s.replace('"', '\\"')
s = re.sub(r"(?<= ) ", "\\ ", s)
return '"' + s + '"'
def quote_unicode(s):
s = s.replace("\\", "\\\\")
s = s.replace('"', '\\"')
s = s.replace("\a", "\\a")
s = s.replace("\b", "\\b")
s = s.replace("\f", "\\f")
s = s.replace("\n", "\\n")
s = s.replace("\r", "\\r")
s = s.replace("\t", "\\t")
s = s.replace("\v", "\\v")
return s

View File

@@ -0,0 +1,27 @@
import os
import renpy
def shorten_filename(filename):
"""
Shortens a file name. Returns the shortened filename, and a flag that says
if the filename is in the common directory.
"""
commondir = os.path.normpath(renpy.config.commondir) # type: ignore
gamedir = os.path.normpath(renpy.config.gamedir)
if filename.startswith(commondir):
fn = os.path.relpath(filename, commondir)
common = True
elif filename.startswith(gamedir):
fn = os.path.relpath(filename, gamedir)
common = False
else:
fn = os.path.basename(filename)
common = False
return fn, common

View File

@@ -0,0 +1,9 @@
STRING_RE = r"""(?x)
\b(?:_|__|___|_p)
\s*(\((?:[\s\\\n]*[uU]?(?:
\"\"\"(?:\\.|\\\n|\"{1,2}|[^\\"])*?\"\"\"
|'''(?:\\.|\\\n|\'{1,2}|[^\\'])*?'''
|"(?:\\.|\\\n|[^\\"])*"
|'(?:\\.|\\\n|[^\\'])*'
))+\s*\))
"""

View File

@@ -0,0 +1,96 @@
from .object import Object
class Action(Object):
pass
class BarValue(Object):
pass
class Addable(object):
pass
class Layer(Addable):
pass
class Many(Addable):
pass
class One(Addable):
pass
class Detached(Addable):
pass
class ChildOrFixed(Addable):
pass
def get_code(self, **kwargs) -> str:
pass
def _hotspot(spot, style="hotspot", **properties):
pass
def _imagebutton(
idle_image=None,
hover_image=None,
insensitive_image=None,
activate_image=None,
selected_idle_image=None,
selected_hover_image=None,
selected_insensitive_image=None,
selected_activate_image=None,
idle=None,
hover=None,
insensitive=None,
selected_idle=None,
selected_hover=None,
selected_insensitive=None,
image_style=None,
auto=None,
**properties,
):
pass
def _imagemap(
ground,
selected,
hotspots,
unselected=None,
overlays=False,
style="imagemap",
mouse="imagemap",
with_none=None,
**properties,
):
pass
def _textbutton(
label,
clicked=None,
style=None,
text_style=None,
substitute=True,
scope=None,
**kwargs,
):
pass
def _key(key, action=None, activate_sound=None):
pass
def _label(label, style=None, text_style=None, substitute=True, scope=None, **kwargs):
pass

View File

@@ -0,0 +1,250 @@
from . import ast
from collections import deque
IDENT_CHAR = " "
def indent(code: str, level: int = 1) -> str:
"""
Indent each line of code by a specified level.
Args:
code: The string to indent
level: Number of indentation levels to apply (default: 1)
Returns:
The indented string
"""
if not code:
return ""
indent_str = IDENT_CHAR * level
# add indentation to each non-empty line
# keep empty lines as they are
rv = [indent_str + line if line.strip() else line for line in code.split("\n")]
return "\n".join(rv)
def get_code_properties(props: tuple | dict, newline: bool = False, **kwargs) -> str:
"""
:param keyword: tuple | dict
:param newline: bool
:return: str
>>> get_code_properties((("a", 1), ("b", 2)))
a 1 b 2
>>> get_code_properties((("a", 1), (None, b)), newline=True)
a 1
b
>>> get_code_properties({"a": 1, "b": None})
a 1
b None
"""
list = []
if isinstance(props, dict):
props = props.items()
for prop in props:
if isinstance(prop, tuple) and len(prop) == 2:
key, value = prop[0], prop[1]
valstr = get_code(value, **kwargs)
if not valstr:
list.append(f"{key}")
else:
list.append(f"{key} {valstr}")
continue
else:
prop_str = " ".join([str(x) for x in prop])
if not prop_str:
continue
list.append(prop_str)
return ("\n" if newline else " ").join(list)
def get_code(node, **kwargs) -> str:
"""
Parameters
----------
node : ast.Node
kwargs : dict
indent : int
space indent level
modifier : Callable[[ast.Node], ast.Node]
modify node before get code
Returns
-------
str
generated code
Raises
------
NotImplementedError
if node type is not implemented or some attributes unable to handle.
"""
if isinstance(node, str):
return node
if isinstance(node, list):
rv = []
items = deque(node)
while items:
item = items.popleft()
# [Say|UserStatement?, Menu]
if isinstance(item, ast.Say) or isinstance(item, ast.UserStatement):
next = items[0] if items else None
if isinstance(next, ast.Menu) and attr(next, "statement_start") == item:
menu = items.popleft()
call_kwargs = kwargs.copy()
call_kwargs["menu_say"] = item
rv.append(get_code(menu, **call_kwargs))
continue
# [Label, Say|UserStatement?, Menu]
if isinstance(item, ast.Label):
next = items[0] if items else None
# [Label, Say|UserStatement, Menu]
if isinstance(next, ast.Say) or isinstance(next, ast.UserStatement):
next_next = items[1] if len(items) > 1 else None
if (
isinstance(next_next, ast.Menu)
and attr(next_next, "statement_start") == item
):
say = items.popleft()
menu = items.popleft()
call_kwargs = kwargs.copy()
call_kwargs["menu_label"] = item
call_kwargs["menu_say"] = say
rv.append(get_code(menu, **call_kwargs))
continue
# [Label, Menu]
if isinstance(next, ast.Menu) and attr(next, "statement_start") == item:
menu = items.popleft()
call_kwargs = kwargs.copy()
call_kwargs["menu_label"] = item
rv.append(get_code(menu, **call_kwargs))
continue
if isinstance(item, ast.With):
# some node quoted by two ast.With if has with suffix expr, the sdk snippet:
#
# renpy.parser.parse_with(node):
# if not "with":
# return node
# expr = simple_expression
# return [ast.With(loc, "None", paired=expr), node, ast.With(loc, expr)]
#
# - ast.Scene
# - ast.Show
# - ast.Hide
expr, paired = attr(item, "expr"), attr(item, "paired")
if (not expr or expr == "None") and paired:
next_next = items[1] if len(items) > 1 else None
if (
isinstance(next_next, ast.With)
and attr(next_next, "expr") == paired
):
node = items.popleft()
close_with = items.popleft()
call_kwargs = kwargs.copy()
call_kwargs.update({"with_expr": attr(close_with, "expr")})
rv.append(get_code(node, **call_kwargs))
continue
if isinstance(item, ast.Call):
# call = [Call, Label?, Pass]
from_label = None
# try to pop next Label|Pass
next = items.popleft() if items else None
if isinstance(next, ast.Label):
from_label = next
# pop Pass
items.popleft() if items else None
if from_label is not None:
call_kwargs = kwargs.copy()
call_kwargs["from_label"] = from_label
rv.append(get_code(item, **call_kwargs))
continue
if isinstance(item, ast.Return):
if (
# it cant tell out an anto appended Return or an empty one
# currently we must not ignore
False
and len(node) > 1
and not items
and not attr(item, "expression")
# omit the return after While may cause unexpected behavior
and not any(isinstance(n, ast.While) for n in node)
):
# ignore the return
continue
# if all left items are returns
if all(
isinstance(n, ast.Return) and not attr(n, "expression")
for n in items
):
# ignore all returns
rv.append(get_code(item, **kwargs))
break
# if isinstance(item, ast.Init):
# if item.priority == 500:
# if len(item.block) == 1 and isinstance(item.block[0], ast.Image):
# rv.append(get_code(item.block[0]))
# continue
rv.append(get_code(item, **kwargs))
return "\n".join(rv)
# modify node before get code
modifier = kwargs.get("modifier")
if modifier:
modifier(node, **kwargs)
return node.get_code(**kwargs)
def get_block_code(node, **kwargs) -> str:
"""
https://www.renpy.org/doc/html/layeredimage.html#layeredimage
"""
if isinstance(node, list):
return "\n".join(map(lambda x: get_block_code(x, **kwargs), node))
lines = []
if isinstance(node, tuple):
if len(node) == 5:
_, _, _, code, block = node
elif len(node) == 4:
_, _, code, block = node
else:
raise NotImplementedError
lines.append(code)
lines.append(indent(get_block_code(block, **kwargs)))
else:
raise NotImplementedError
return "\n".join(lines)
def attr(item, key: str) -> str:
if isinstance(item, dict):
return item.get(key, None)
if hasattr(item, key):
return getattr(item, key)
return None
def label_code(label: str, child, **kwargs) -> str:
"""
return a code block with a label
example:
>>> label_getcode("label foo", None)
"label foo"
>>> label_getcode("label foo", Expression("bar"))
"label foo:
bar"
"""
if not child:
return label
return f"{label}:\n{indent(get_code(child, **kwargs))}"