Skip to content
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
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,22 @@ Frame: 12 bytes of padding (limit 8)
1 of 3 structs over the padding limit
```

The check exits with status 1 when any struct exceeds the limit. Select the ABI with `--abi lp64` (the default) or `--abi ilp32`; for example:
The check exits with status 1 when any struct exceeds the limit.

`--ignore NAME` is available only with `check` and may be repeated; `--ignore=NAME` is also supported. Ignored structs are omitted from the padding check but remain included in the summary's total. The summary ends with the number of distinct ignored names, so duplicates do not increase that count.

```sh
structpad check --ignore Packet net.sp
```

```text
Frame: 12 bytes of padding (limit 0)
1 of 3 structs over the padding limit (1 ignored)
```

Ignoring both `Packet` and `Frame` prints `ok, 3 structs (2 ignored)` and exits with status 0. Invalid names such as `1x` produce `invalid --ignore "1x"`; a name not defined in the file produces `unknown struct "Nope" in --ignore`. Both errors exit with status 2.

Select the ABI with `--abi lp64` (the default) or `--abi ilp32`; for example:

```sh
structpad layout --abi ilp32 net.sp
Expand Down
40 changes: 33 additions & 7 deletions src/structpad/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
_USAGE = (
"usage: structpad layout [--abi lp64|ilp32] FILE\n"
" structpad suggest [--abi lp64|ilp32] FILE\n"
" structpad check [--abi lp64|ilp32] [--max-padding N] FILE\n"
" structpad check [--abi lp64|ilp32] [--max-padding N] [--ignore NAME]... FILE\n"
)
_MAX_PADDING_RE = re.compile(r"[0-9]{1,6}\Z")
_NAME_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]{0,63}\Z")


def _escape_controls(value: str) -> str:
Expand All @@ -28,9 +29,10 @@ def _write_error(stderr: BinaryIO, message: str) -> None:

def _parse_options(
command: str, arguments: list[str]
) -> tuple[str, int, str] | str:
) -> tuple[str, int, str, tuple[str, ...]] | str:
abi = "lp64"
max_padding = 0
ignored: list[str] = []
file_name: str | None = None
index = 0

Expand Down Expand Up @@ -70,6 +72,22 @@ def _parse_options(
max_padding = int(value)
continue

if argument == "--ignore" or argument.startswith("--ignore="):
if command != "check":
return f"{command} does not take --ignore"
if argument == "--ignore":
if index + 1 >= len(arguments):
return "--ignore needs a value"
value = arguments[index + 1]
index += 2
else:
value = argument[len("--ignore=") :]
index += 1
if _NAME_RE.fullmatch(value) is None:
return f'invalid --ignore "{_escape_controls(value)}"'
ignored.append(value)
continue

if argument.startswith("-") and argument != "-":
return f'unknown option "{_escape_controls(argument)}"'
if file_name is not None:
Expand All @@ -79,7 +97,7 @@ def _parse_options(

if file_name is None:
return "missing FILE"
return abi, max_padding, file_name
return abi, max_padding, file_name, tuple(ignored)


def run(args: list[str], stdin: BinaryIO, stdout: BinaryIO, stderr: BinaryIO) -> int:
Expand All @@ -100,7 +118,7 @@ def run(args: list[str], stdin: BinaryIO, stdout: BinaryIO, stderr: BinaryIO) ->
if isinstance(parsed_options, str):
_write_error(stderr, parsed_options)
return 2
abi, max_padding, file_name = parsed_options
abi, max_padding, file_name, ignored_names = parsed_options
display = "<stdin>" if file_name == "-" else file_name.replace("\\", "/")

try:
Expand All @@ -125,18 +143,26 @@ def run(args: list[str], stdin: BinaryIO, stdout: BinaryIO, stderr: BinaryIO) ->
_write_error(stderr, error.message)
return 2

definitions = {struct.name: struct for struct in structs}
for name in ignored_names:
if name not in definitions:
_write_error(stderr, f'unknown struct "{name}" in --ignore')
return 2

ignored = frozenset(ignored_names)

if command == "layout":
stdout.write(render_layout(structs, abi))
return 0
if command == "suggest":
stdout.write(render_suggest(structs, abi))
return 0

output = render_check(structs, abi, max_padding)
output = render_check(structs, abi, max_padding, ignored)
stdout.write(output)
definitions = {struct.name: struct for struct in structs}
if any(
calculate_layout(struct, abi, definitions).padding > max_padding
struct.name not in ignored
and calculate_layout(struct, abi, definitions).padding > max_padding
for struct in structs
):
return 1
Expand Down
18 changes: 15 additions & 3 deletions src/structpad/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,33 @@ def render_suggest(structs: Sequence[Struct], abi: str) -> bytes:
return ("\n\n".join(blocks) + "\n").encode("utf-8")


def render_check(structs: Sequence[Struct], abi: str, max_padding: int) -> bytes:
def render_check(
structs: Sequence[Struct],
abi: str,
max_padding: int,
ignored: frozenset[str] = frozenset(),
) -> bytes:
"""Render padding-limit results as UTF-8 bytes with LF line endings."""
definitions = _definitions(structs)
over_limit: list[tuple[Struct, int]] = []
for struct in structs:
if struct.name in ignored:
continue
padding = calculate_layout(struct, abi, definitions).padding
if padding > max_padding:
over_limit.append((struct, padding))

ignored_suffix = f" ({len(ignored)} ignored)" if ignored else ""

if not over_limit:
return f"ok, {len(structs)} structs\n".encode()
return f"ok, {len(structs)} structs{ignored_suffix}\n".encode()

lines = []
for struct, padding in over_limit:
byte_word = "byte" if padding == 1 else "bytes"
lines.append(f"{struct.name}: {padding} {byte_word} of padding (limit {max_padding})")
lines.append(f"{len(over_limit)} of {len(structs)} structs over the padding limit")
lines.append(
f"{len(over_limit)} of {len(structs)} structs over the padding limit"
f"{ignored_suffix}"
)
return ("\n".join(lines) + "\n").encode("utf-8")
2 changes: 1 addition & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
_USAGE = (
b"usage: structpad layout [--abi lp64|ilp32] FILE\n"
b" structpad suggest [--abi lp64|ilp32] FILE\n"
b" structpad check [--abi lp64|ilp32] [--max-padding N] FILE\n"
b" structpad check [--abi lp64|ilp32] [--max-padding N] [--ignore NAME]... FILE\n"
)
_MIXED = (
b"struct Mixed {\n"
Expand Down
Loading
Loading