Skip to content

trestle.core.commands.canonicalize

trestle.core.commands.canonicalize ¤

Trestle Canonicalize Command.

Attributes¤

logger = logging.getLogger(__name__) module-attribute ¤

Classes¤

CanonicalizeCmd ¤

Bases: CommandBase


              flowchart TD
              trestle.core.commands.canonicalize.CanonicalizeCmd[CanonicalizeCmd]
              trestle.core.commands.command_docs.CommandBase[CommandBase]

                              trestle.core.commands.command_docs.CommandBase --> trestle.core.commands.canonicalize.CanonicalizeCmd
                


              click trestle.core.commands.canonicalize.CanonicalizeCmd href "" "trestle.core.commands.canonicalize.CanonicalizeCmd"
              click trestle.core.commands.command_docs.CommandBase href "" "trestle.core.commands.command_docs.CommandBase"
            

Canonicalize a JSON document using RFC 8785.

This command reads the raw JSON input directly and writes canonical JSON bytes. It is designed for OSCAL artifacts, but it does not load the input through OSCAL models and can canonicalize any valid JSON document.

Source code in trestle/core/commands/canonicalize.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
class CanonicalizeCmd(CommandBase):
    """Canonicalize a JSON document using RFC 8785.

    This command reads the raw JSON input directly and writes canonical JSON
    bytes. It is designed for OSCAL artifacts, but it does not load the input
    through OSCAL models and can canonicalize any valid JSON document.
    """

    name = 'canonicalize'

    def _init_arguments(self) -> None:
        self.add_argument(
            '-f', '--file', help='Path to the JSON document to canonicalize.', required=True, type=pathlib.Path
        )
        self.add_argument(
            '-o',
            '--output',
            help='Output file for canonical JSON. If omitted, output is written to stdout.',
            type=pathlib.Path,
            default=None,
        )

    def _run(self, args: argparse.Namespace) -> int:
        """Canonicalize a JSON document."""
        try:
            log.set_log_level_from_args(args)
            self.canonicalize(args.file, args.output)
            return CmdReturnCodes.SUCCESS.value
        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(e, logger, 'Error while canonicalizing JSON')

    @classmethod
    def canonicalize(cls, input_path: pathlib.Path, output_path: Optional[pathlib.Path] = None) -> None:
        """Write RFC 8785 canonical JSON bytes for input_path to output_path or stdout."""
        input_path = input_path.resolve()
        if input_path.suffix.lower() != '.json':
            raise TrestleError('Canonicalization is only supported for JSON files.')

        _, canonical_bytes = load_canonical_json_file(input_path)

        if output_path is None:
            cls._write_stdout(canonical_bytes)
            return

        output_path = output_path.resolve()
        if output_path.exists() and output_path.is_dir():
            raise TrestleError(f'Canonical JSON output path is a directory: {output_path}')

        output_path.parent.mkdir(parents=True, exist_ok=True)
        output_path.write_bytes(canonical_bytes)

    @staticmethod
    def _write_stdout(canonical_bytes: bytes) -> None:
        """Write canonical bytes to stdout."""
        try:
            sys.stdout.buffer.write(canonical_bytes)
        except AttributeError:
            sys.stdout.write(canonical_bytes.decode())
Attributes¤
name = 'canonicalize' class-attribute instance-attribute ¤
Methods:¤
canonicalize(input_path, output_path=None) classmethod ¤

Write RFC 8785 canonical JSON bytes for input_path to output_path or stdout.

Source code in trestle/core/commands/canonicalize.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@classmethod
def canonicalize(cls, input_path: pathlib.Path, output_path: Optional[pathlib.Path] = None) -> None:
    """Write RFC 8785 canonical JSON bytes for input_path to output_path or stdout."""
    input_path = input_path.resolve()
    if input_path.suffix.lower() != '.json':
        raise TrestleError('Canonicalization is only supported for JSON files.')

    _, canonical_bytes = load_canonical_json_file(input_path)

    if output_path is None:
        cls._write_stdout(canonical_bytes)
        return

    output_path = output_path.resolve()
    if output_path.exists() and output_path.is_dir():
        raise TrestleError(f'Canonical JSON output path is a directory: {output_path}')

    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_bytes(canonical_bytes)

Functions:¤

handler: python