From 698174a30d03f78ff41870b943f4e1f4889b3f3c Mon Sep 17 00:00:00 2001 From: Andrea Zoppi Date: Thu, 7 Mar 2024 21:06:50 +0100 Subject: [PATCH] Added `align` method Signed-off-by: Andrea Zoppi --- docs/_autosummary/hexrec.base.BaseFile.rst | 1 + .../hexrec.formats.asciihex.AsciiHexFile.rst | 1 + .../hexrec.formats.avr.AvrFile.rst | 1 + .../hexrec.formats.ihex.IhexFile.rst | 1 + .../hexrec.formats.mos.MosFile.rst | 1 + .../hexrec.formats.raw.RawFile.rst | 1 + .../hexrec.formats.srec.SrecFile.rst | 1 + .../hexrec.formats.titxt.TiTxtFile.rst | 1 + .../hexrec.formats.xtek.XtekFile.rst | 1 + docs/requirements.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- src/hexrec/base.py | 53 ++++++++++ src/hexrec/cli.py | 96 +++++++++++++++---- tests/test_base.py | 8 ++ .../test_hexrec_align_-m_3_triangle.mot | 11 +++ tests/test_cli/test_hexrec_align_triangle.mot | 11 +++ tests/test_cli/triangle.mot | 11 +++ 18 files changed, 182 insertions(+), 23 deletions(-) create mode 100644 tests/test_cli/test_hexrec_align_-m_3_triangle.mot create mode 100644 tests/test_cli/test_hexrec_align_triangle.mot create mode 100644 tests/test_cli/triangle.mot diff --git a/docs/_autosummary/hexrec.base.BaseFile.rst b/docs/_autosummary/hexrec.base.BaseFile.rst index 54f24f0..c8e6248 100644 --- a/docs/_autosummary/hexrec.base.BaseFile.rst +++ b/docs/_autosummary/hexrec.base.BaseFile.rst @@ -35,6 +35,7 @@ BaseFile :nosignatures: ~BaseFile.__init__ + ~BaseFile.align ~BaseFile.append ~BaseFile.apply_records ~BaseFile.clear diff --git a/docs/_autosummary/hexrec.formats.asciihex.AsciiHexFile.rst b/docs/_autosummary/hexrec.formats.asciihex.AsciiHexFile.rst index a6fad4d..ecf32c7 100644 --- a/docs/_autosummary/hexrec.formats.asciihex.AsciiHexFile.rst +++ b/docs/_autosummary/hexrec.formats.asciihex.AsciiHexFile.rst @@ -34,6 +34,7 @@ AsciiHexFile :nosignatures: ~AsciiHexFile.__init__ + ~AsciiHexFile.align ~AsciiHexFile.append ~AsciiHexFile.apply_records ~AsciiHexFile.clear diff --git a/docs/_autosummary/hexrec.formats.avr.AvrFile.rst b/docs/_autosummary/hexrec.formats.avr.AvrFile.rst index 2ccf1f0..c680bf3 100644 --- a/docs/_autosummary/hexrec.formats.avr.AvrFile.rst +++ b/docs/_autosummary/hexrec.formats.avr.AvrFile.rst @@ -34,6 +34,7 @@ AvrFile :nosignatures: ~AvrFile.__init__ + ~AvrFile.align ~AvrFile.append ~AvrFile.apply_records ~AvrFile.clear diff --git a/docs/_autosummary/hexrec.formats.ihex.IhexFile.rst b/docs/_autosummary/hexrec.formats.ihex.IhexFile.rst index 928e405..4eafa20 100644 --- a/docs/_autosummary/hexrec.formats.ihex.IhexFile.rst +++ b/docs/_autosummary/hexrec.formats.ihex.IhexFile.rst @@ -36,6 +36,7 @@ IhexFile :nosignatures: ~IhexFile.__init__ + ~IhexFile.align ~IhexFile.append ~IhexFile.apply_records ~IhexFile.clear diff --git a/docs/_autosummary/hexrec.formats.mos.MosFile.rst b/docs/_autosummary/hexrec.formats.mos.MosFile.rst index 38c28b7..93962fa 100644 --- a/docs/_autosummary/hexrec.formats.mos.MosFile.rst +++ b/docs/_autosummary/hexrec.formats.mos.MosFile.rst @@ -34,6 +34,7 @@ MosFile :nosignatures: ~MosFile.__init__ + ~MosFile.align ~MosFile.append ~MosFile.apply_records ~MosFile.clear diff --git a/docs/_autosummary/hexrec.formats.raw.RawFile.rst b/docs/_autosummary/hexrec.formats.raw.RawFile.rst index 426fef7..140b3a3 100644 --- a/docs/_autosummary/hexrec.formats.raw.RawFile.rst +++ b/docs/_autosummary/hexrec.formats.raw.RawFile.rst @@ -34,6 +34,7 @@ RawFile :nosignatures: ~RawFile.__init__ + ~RawFile.align ~RawFile.append ~RawFile.apply_records ~RawFile.clear diff --git a/docs/_autosummary/hexrec.formats.srec.SrecFile.rst b/docs/_autosummary/hexrec.formats.srec.SrecFile.rst index a497bfe..bfcf53a 100644 --- a/docs/_autosummary/hexrec.formats.srec.SrecFile.rst +++ b/docs/_autosummary/hexrec.formats.srec.SrecFile.rst @@ -36,6 +36,7 @@ SrecFile :nosignatures: ~SrecFile.__init__ + ~SrecFile.align ~SrecFile.append ~SrecFile.apply_records ~SrecFile.clear diff --git a/docs/_autosummary/hexrec.formats.titxt.TiTxtFile.rst b/docs/_autosummary/hexrec.formats.titxt.TiTxtFile.rst index b40d1ad..3ef3a15 100644 --- a/docs/_autosummary/hexrec.formats.titxt.TiTxtFile.rst +++ b/docs/_autosummary/hexrec.formats.titxt.TiTxtFile.rst @@ -34,6 +34,7 @@ TiTxtFile :nosignatures: ~TiTxtFile.__init__ + ~TiTxtFile.align ~TiTxtFile.append ~TiTxtFile.apply_records ~TiTxtFile.clear diff --git a/docs/_autosummary/hexrec.formats.xtek.XtekFile.rst b/docs/_autosummary/hexrec.formats.xtek.XtekFile.rst index 2ebcf89..c1e79ac 100644 --- a/docs/_autosummary/hexrec.formats.xtek.XtekFile.rst +++ b/docs/_autosummary/hexrec.formats.xtek.XtekFile.rst @@ -35,6 +35,7 @@ XtekFile :nosignatures: ~XtekFile.__init__ + ~XtekFile.align ~XtekFile.append ~XtekFile.apply_records ~XtekFile.clear diff --git a/docs/requirements.txt b/docs/requirements.txt index fc4f62b..4b144e9 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,7 +2,7 @@ sphinx >= 7 sphinx-click sphinx-autodoc-typehints -bytesparse >= 0.0.8 +bytesparse >= 1.0.0 colorama furo twine diff --git a/pyproject.toml b/pyproject.toml index b9be302..34cea37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ classifiers = [ "Topic :: Utilities", ] dependencies = [ - "bytesparse >= 0.0.8", # comment out and use the one below for development instead + "bytesparse >= 1.0.0", # comment out and use the one below for development instead # "bytesparse @ git+https://github.com/TexZK/bytesparse.git", # only for development "click", "colorama", diff --git a/requirements.txt b/requirements.txt index 363900b..bebbed6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -bytesparse >= 0.0.8 +bytesparse >= 1.0.0 click colorama diff --git a/src/hexrec/base.py b/src/hexrec/base.py index 871ca44..69ddcce 100644 --- a/src/hexrec/base.py +++ b/src/hexrec/base.py @@ -1974,6 +1974,59 @@ def _is_line_empty(cls, line: AnyBytes) -> bool: return not line or line.isspace() + def align( + self, + modulo: int, + start: Optional[int] = None, + endex: Optional[int] = None, + pattern: Union[int, AnyBytes] = 0, + ) -> Self: + r"""Pads blocks to align their boundaries. + + It fills memory holes of the underlying :attr:`memory` within the + specified range with a `pattern`, so that memory blocks are aligned to + the required `modulo`. + + Any stored :attr:`records` are discarded upon return. + + Args: + modulo (int): + Alignment modulo. + + start (int): + Inclusive start address of the specified range. + If ``None``, start from the beginning of the :attr:`memory`. + + endex (int): + Exclusive end address of the specified range. + If ``None``, extend after the end of the :attr:`memory`. + + pattern (bytes or int): + Byte pattern for flooding. + + Returns: + :class:`BaseFile`: *self*. + + See Also: + :attr:`memory` + :meth:`discard_records` + :meth:`bytesparse.base.MutableMemory.align` + + Examples: + **NOTE:** These examples are provided by :class:`BaseFile`. + Inherited classes for specific *formats* may require an adaptation. + + >>> from hexrec import SrecFile + >>> file = SrecFile.from_blocks([[123, b'abc'], [134, b'xyz']]) + >>> _ = file.align(4, pattern=b'.') + >>> file.memory.to_blocks() + [[120, b'...abc..'], [132, b'..xyz...']] + """ + + self.memory.align(modulo, start=start, endex=endex, pattern=pattern) + self.discard_records() + return self + def append(self, item: Union[AnyBytes, int]) -> Self: r"""Appends a byte. diff --git a/src/hexrec/cli.py b/src/hexrec/cli.py index ecf88b0..51996b7 100644 --- a/src/hexrec/cli.py +++ b/src/hexrec/cli.py @@ -321,6 +321,9 @@ def main() -> None: Forces the output file format. By default it is that of the input file. """) +@click.option('-m', '--modulo', type=BYTE_INT, default=4, help=""" + Alignment modulo. +""") @click.option('-s', '--start', type=BASED_INT, help=""" Inclusive start address. Negative values are referred to the end of the data. By default it applies from the start of the data contents. @@ -329,12 +332,65 @@ def main() -> None: Exclusive end address. Negative values are referred to the end of the data. By default it applies till the end of the data contents. """) +@click.option('-v', '--value', type=BYTE_INT, default=0, help=""" + Byte value used to flood alignment padding. +""") @click.option('-w', '--width', type=BASED_INT, help=""" Sets the length of the record data field, in bytes. By default it is that of the input file. """) -@click.argument('infile', type=FILE_PATH_IN) -@click.argument('outfile', type=FILE_PATH_OUT) +@click.argument('infile', type=FILE_PATH_IN, required=False) +@click.argument('outfile', type=FILE_PATH_OUT, required=False) +def align( + input_format: Optional[str], + output_format: Optional[str], + modulo: int, + start: Optional[int], + endex: Optional[int], + value: Optional[int], + width: Optional[int], + infile: str, + outfile: str, +) -> None: + r"""Pads blocks to align their boundaries. + + ``INFILE`` is the path of the input file. + Set to ``-`` to read from standard input; input format required. + + ``OUTFILE`` is the path of the output file. + Set to ``-`` to write to standard output. + Leave empty to overwrite ``INFILE``. + """ + + with SingleFileInOutCtxMgr(infile, input_format, outfile, output_format, width) as ctx: + ctx.output_file.align(modulo, start=start, endex=endex, pattern=value) + + +# ---------------------------------------------------------------------------- + +@main.command() +@click.option('-i', '--input-format', type=FORMAT_CHOICE, help=""" + Forces the input file format. + Required for the standard input. +""") +@click.option('-o', '--output-format', type=FORMAT_CHOICE, help=""" + Forces the output file format. + By default it is that of the input file. +""") +@click.option('-s', '--start', type=BASED_INT, help=""" + Inclusive start address. Negative values are referred to the end of the data. + By default it applies from the start of the data contents. +""") +@click.option('-e', '--endex', type=BASED_INT, help=""" + Exclusive end address. Negative values are referred to the end of the data. + By default it applies till the end of the data contents. +""") +@click.option('-w', '--width', type=BASED_INT, help=""" + Sets the length of the record data field, in bytes. + By default it is that of the input file. +""") +@click.argument('infile', type=FILE_PATH_IN, required=False) +@click.argument('outfile', type=FILE_PATH_OUT, required=False) def clear( input_format: Optional[str], output_format: Optional[str], @@ -373,8 +429,8 @@ def clear( Sets the length of the record data field, in bytes. By default it is that of the input file. """) -@click.argument('infile', type=FILE_PATH_IN) -@click.argument('outfile', type=FILE_PATH_OUT) +@click.argument('infile', type=FILE_PATH_IN, required=False) +@click.argument('outfile', type=FILE_PATH_OUT, required=False) def convert( input_format: Optional[str], output_format: Optional[str], @@ -424,8 +480,8 @@ def convert( Sets the length of the record data field, in bytes. By default it is that of the input file. """) -@click.argument('infile', type=FILE_PATH_IN) -@click.argument('outfile', type=FILE_PATH_OUT) +@click.argument('infile', type=FILE_PATH_IN, required=False) +@click.argument('outfile', type=FILE_PATH_OUT, required=False) def crop( input_format: Optional[str], output_format: Optional[str], @@ -476,8 +532,8 @@ def crop( Sets the length of the record data field, in bytes. By default it is that of the input file. """) -@click.argument('infile', type=FILE_PATH_IN) -@click.argument('outfile', type=FILE_PATH_OUT) +@click.argument('infile', type=FILE_PATH_IN, required=False) +@click.argument('outfile', type=FILE_PATH_OUT, required=False) def delete( input_format: Optional[str], output_format: Optional[str], @@ -527,8 +583,8 @@ def delete( Sets the length of the record data field, in bytes. By default it is that of the input file. """) -@click.argument('infile', type=FILE_PATH_IN) -@click.argument('outfile', type=FILE_PATH_OUT) +@click.argument('infile', type=FILE_PATH_IN, required=False) +@click.argument('outfile', type=FILE_PATH_OUT, required=False) def fill( input_format: Optional[str], output_format: Optional[str], @@ -579,8 +635,8 @@ def fill( Sets the length of the record data field, in bytes. By default it is that of the input file. """) -@click.argument('infile', type=FILE_PATH_IN) -@click.argument('outfile', type=FILE_PATH_OUT) +@click.argument('infile', type=FILE_PATH_IN, required=False) +@click.argument('outfile', type=FILE_PATH_OUT, required=False) def flood( input_format: Optional[str], output_format: Optional[str], @@ -941,8 +997,8 @@ def merge( Sets the length of the record data field, in bytes. By default it is that of the input file. """) -@click.argument('infile', type=FILE_PATH_IN) -@click.argument('outfile', type=FILE_PATH_OUT) +@click.argument('infile', type=FILE_PATH_IN, required=False) +@click.argument('outfile', type=FILE_PATH_OUT, required=False) def shift( input_format: Optional[str], output_format: Optional[str], @@ -972,7 +1028,7 @@ def shift( Forces the input file format. Required for the standard input. """) -@click.argument('infile', type=FILE_PATH_IN) +@click.argument('infile', type=FILE_PATH_IN, required=False) def validate( input_format: Optional[str], infile: str, @@ -1001,7 +1057,7 @@ def srec() -> None: @srec.command() @click.option('-f', '--format', 'format', type=DATA_FMT_CHOICE, default='ascii', help='Header data format.') -@click.argument('infile', type=FILE_PATH_IN) +@click.argument('infile', type=FILE_PATH_IN, required=False) def get_header( format: str, infile: str, @@ -1028,8 +1084,8 @@ def get_header( @click.option('-f', '--format', 'format', type=DATA_FMT_CHOICE, default='ascii', help='Header data format.') @click.argument('header', type=str) -@click.argument('infile', type=FILE_PATH_IN) -@click.argument('outfile', type=FILE_PATH_OUT) +@click.argument('infile', type=FILE_PATH_IN, required=False) +@click.argument('outfile', type=FILE_PATH_OUT, required=False) def set_header( format: str, header: str, @@ -1071,8 +1127,8 @@ def set_header( # noinspection PyShadowingBuiltins @srec.command() -@click.argument('infile', type=FILE_PATH_IN) -@click.argument('outfile', type=FILE_PATH_OUT) +@click.argument('infile', type=FILE_PATH_IN, required=False) +@click.argument('outfile', type=FILE_PATH_OUT, required=False) def del_header( infile: str, outfile: str, diff --git a/tests/test_base.py b/tests/test_base.py index 40cf9b7..54bf1c5 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -983,6 +983,14 @@ def test__is_line_empty(self): assert File._is_line_empty(b'') is True assert File._is_line_empty(b' \t\v\r\n') is True + def test_align(self): + File = self.File + file = File.from_blocks([[123, b'abc'], [134, b'xyz']]) + returned = file.align(4, pattern=b'.') + assert returned is file + assert file._memory.to_blocks() == [[120, b'...abc..'], [132, b'..xyz...']] + assert file._records is None + def test_append(self): File = self.File file = File.from_bytes(b'abc', offset=5) diff --git a/tests/test_cli/test_hexrec_align_-m_3_triangle.mot b/tests/test_cli/test_hexrec_align_-m_3_triangle.mot new file mode 100644 index 0000000..02527da --- /dev/null +++ b/tests/test_cli/test_hexrec_align_-m_3_triangle.mot @@ -0,0 +1,11 @@ +S0030000FC +S1130000000102030405060708090A0B0C0D0E0F74 +S1130010000102030405060708090A0B0C0D0E0073 +S1130020000002030405060708090A0B0C0D000072 +S10F0033030405060708090A0B0C000072 +S10F004200000405060708090A0B000072 +S10C00540005060708090A000072 +S109006606070809000072 +S109007500000708000072 +S5030008F4 +S9030000FC diff --git a/tests/test_cli/test_hexrec_align_triangle.mot b/tests/test_cli/test_hexrec_align_triangle.mot new file mode 100644 index 0000000..ef788f1 --- /dev/null +++ b/tests/test_cli/test_hexrec_align_triangle.mot @@ -0,0 +1,11 @@ +S0030000FC +S1130000000102030405060708090A0B0C0D0E0F74 +S1130010000102030405060708090A0B0C0D0E0073 +S1130020000002030405060708090A0B0C0D000072 +S1130030000000030405060708090A0B0C00000071 +S10B00440405060708090A0B74 +S10B00540005060708090A0073 +S10B0064000006070809000072 +S10B0074000000070800000071 +S5030008F4 +S9030000FC diff --git a/tests/test_cli/triangle.mot b/tests/test_cli/triangle.mot new file mode 100644 index 0000000..f86d6af --- /dev/null +++ b/tests/test_cli/triangle.mot @@ -0,0 +1,11 @@ +S0030000FC +S1130000000102030405060708090A0B0C0D0E0F74 +S11100110102030405060708090A0B0C0D0E74 +S10F002202030405060708090A0B0C0D74 +S10D0033030405060708090A0B0C74 +S10B00440405060708090A0B74 +S109005505060708090A74 +S10700660607080974 +S1050077070874 +S5030008F4 +S9030000FC