simvx.core.scene_io.layout

Where an emitted statement breaks its lines.

A scene file is code the author reads, edits and formats alongside everything else in their project, so what this layer writes has to look like the rest of it. A constructor carrying a material, a texture and a transform runs well past any sane column on one line; broken up by hand it would read one keyword argument per line, and that is what :func:wrap_statement produces.

The rule implemented here is the one a formatter run at :data:LINE_LIMIT columns applies, so a file this layer writes and a file the author formatted are the same file:

  • a statement that fits on one line stays on one line;

  • a call that does not puts its arguments on a single indented line of their own, if they fit there;

  • one that still does not puts each argument on its own line, with a trailing comma, descending into any argument that is itself too long;

  • a list, tuple or dict that has to break at all breaks all the way: a collection with more than one item never takes the single-line-of-its-own form a call can;

  • a group whose items already end in a comma stays broken out however short it is, because that comma is how an author says so – unless the comma is what makes the value what it is, as in a one-item tuple.

Width is counted in columns rather than characters throughout (:func:display_width), which is what a limit is and what the formatter counts: a construction carrying a Chinese label is written wider than it is long, and measured by its length would be left whole well past the limit.

An import line is the one statement that departs from the rule, and

func:

wrap_import is where it does: it never takes the single-line-of-its-own form, because the comma a broken import ends up with is one the author could have typed and therefore means what theirs means.

Everything here is text in, text out: no parse, no tree. The emitter builds the one-line form it always did and hands it over; :mod:~simvx.core.scene_io.edits applies the same rule to a call it has just edited in an existing file.

Module Contents

Functions

display_width

The columns text occupies, which is not always how many characters it has.

wrap_statement

text laid out to fit limit columns, starting at column indent.

wrap_import

from <module> import ... for names, laid out to fit limit.

split_call

(head, items, closer) for the bracketed group text ends with.

split_items

The comma-separated items of a bracket body, in order.

ends_with_comma

Does this bracket body end in a comma the layout has to keep?

trailing_comma_is_syntax

Is this group’s trailing comma part of the value rather than a request?

carries_magic_comma

Does text hold, at any depth, a comma asking for the broken-out form?

is_collection_head

Is the group head opens a collection literal rather than a call?

call_layout

How a bracketed group with these items should be laid out.

Data

API

simvx.core.scene_io.layout.__all__

[‘LINE_LIMIT’, ‘call_layout’, ‘carries_magic_comma’, ‘display_width’, ‘ends_with_comma’, ‘is_collect…

simvx.core.scene_io.layout.LINE_LIMIT

120

simvx.core.scene_io.layout.INDENT

4

simvx.core.scene_io.layout.display_width(text: str) int[source]

The columns text occupies, which is not always how many characters it has.

A limit is a count of columns, and East Asian script does not spend one character per column: a label of Chinese measured with :func:len comes out half the width it is written at, and a line of it is left alone well past the limit. Every width this module compares against a limit is counted here instead, and so is every width :mod:~simvx.core.scene_io.edits measures off a parsed file.

A character is two columns wide when its East Asian width is wide or fullwidth, and one otherwise. The few wide characters that attach to the one in front of them – a Japanese voiced-sound mark, an emoji skin tone – are not counted wide, being written over their neighbour rather than beside it. That is the count the project’s formatter makes, for every character the standard library’s Unicode tables know about.

simvx.core.scene_io.layout.wrap_statement(text: str, *, indent: int = 0, limit: int = LINE_LIMIT, explode: bool = False, suffix: str = '') str[source]

text laid out to fit limit columns, starting at column indent.

text is one statement written on a single line – hero = Sprite2D(...), super().__init__(...), self.add_child(hero). The returned string carries no indent on its first line, because the caller is already placing that statement at indent; every continuation line it contains is indented in full.

explode forces the one-argument-per-line form regardless of width, which is what an existing call’s trailing comma asks for.

suffix is what follows the group on its last line without being part of it – the : of class Hero(Node2D): – and comes back on the end. Passing it rather than writing it in is what lets the group be found and broken at all, and its width counts against limit like everything else sharing the line.

A statement with nothing to break – one ending in no bracketed group, or in an empty one – comes back as it went in, over the limit: reflowing the inside of a value is not this layer’s business, and a line that cannot be broken is better left long than broken wrongly. A group whose one argument is itself too long for any line still gets broken open around it, which is where the argument has the most room, and is what a formatter does with it.

simvx.core.scene_io.layout.wrap_import(module: str, names: list[str], *, limit: int = LINE_LIMIT, explode: bool = False) str[source]

from <module> import ... for names, laid out to fit limit.

One line for as long as the names fit on one, without the brackets a file may have had round them: a pair no longer holding anything apart is a pair a formatter takes off. Past that the names go inside brackets, one per line, each with a comma after it, and never onto the single shared line of their own that a call’s arguments may take.

That last is the one place an import parts company with a call, and the reason is the comma. A broken group’s trailing comma is how an author asks for a line per item (:func:carries_magic_comma), and an import may carry one – from x import (a, b,) is legal where from x import a, b, is not – so the comma this layout writes would be read back as that request the next time the line is laid out. Writing the form that request asks for is what keeps a second pass over an import agreeing with the first.

explode is that request, found on an import already in the file.

names are written in the order given, and the statement is written at column zero: an import is a module-level statement.

simvx.core.scene_io.layout.split_call(text: str) tuple[str, list[str], str] | None[source]

(head, items, closer) for the bracketed group text ends with.

Sprite2D(name='Hero', position=Vec2(1.0, 2.0)) splits into "Sprite2D(", the two arguments, and ")". The group taken is the last one that opens at the top level and closes on the final character, which is what makes super().__init__(...) split at __init__ rather than at the empty super() in front of it, and self.add_child(Sprite2D(...)) split at add_child rather than at the construction inside it.

None when text does not end in a bracket that opened at the top level: there is then no group to break. Brackets and commas inside string literals are text and are skipped.

simvx.core.scene_io.layout.split_items(body: str) list[str][source]

The comma-separated items of a bracket body, in order.

Commas inside nested brackets and inside string literals do not separate, and neither do the ones between a lambda’s parameters: lambda a, b: x is one argument, and breaking the line at its comma would leave a fragment that is not an expression at all. A trailing comma yields no empty final item; ask :func:ends_with_comma about the one that was there.

simvx.core.scene_io.layout.ends_with_comma(body: str) bool[source]

Does this bracket body end in a comma the layout has to keep?

For a one-item tuple the comma is what makes it a tuple, so dropping it while breaking the line would change the value the file loads back.

simvx.core.scene_io.layout.trailing_comma_is_syntax(opener: str, *, collection: bool, item_count: int) bool[source]

Is this group’s trailing comma part of the value rather than a request?

An author’s comma after the last of several arguments is a request to keep them on separate lines. Two commas are not requests at all, because without them the expression means something else: the one in a one-item tuple (value,), and the one in a one-item subscript frames[0,], each of which is what makes that expression a tuple. A one-item list, dict or call has no such excuse and is broken out like any other.

simvx.core.scene_io.layout.carries_magic_comma(text: str) bool[source]

Does text hold, at any depth, a comma asking for the broken-out form?

A comma an author left after the last item does not only break the group it is in: everything that group sits inside breaks with it, or the group could not have its own lines. So the question has to be asked of a whole value before a line is chosen for it, not only of the group being laid out.

simvx.core.scene_io.layout.is_collection_head(head: str) bool[source]

Is the group head opens a collection literal rather than a call?

[ and { and ( mean one thing after a name and another after an =: Vec2(1, 2) and ART['icon'] are a call and a subscript, while [1, 2] and (1, 2) are a list and a tuple. What decides is the character in front of the bracket, and what turns on it is whether the group may put its items on a single line of their own: a collection that has to break at all breaks one item per line.

simvx.core.scene_io.layout.call_layout(items: list[str], *, indent: int, prefix_width: int, suffix_width: int = 0, limit: int = LINE_LIMIT, explode: bool = False, collection: bool = False, extra: int = 0) str[source]

How a bracketed group with these items should be laid out.

"line" keeps the whole group on the line it starts on, "row" puts every item on one indented line of its own, "column" gives each item a line and a trailing comma. prefix_width is the column the group’s opening bracket sits at plus one – everything already written on that line – and suffix_width what follows its closing bracket, since both share the line and both count against limit. collection marks a list, tuple or dict literal, which takes "column" wherever a call would take "row", unless it holds a single item and so has nothing to separate. extra is what the body carries beyond the items themselves: the one column of a retained trailing comma, without which a group would be judged a character narrower than it is going to be written.

Shared with :func:wrap_statement so that a call laid out from text and one laid out by editing a parsed file arrive at the same answer.