ihexer 0.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ihexer-0.0.0/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+
2
+ MIT License
3
+
4
+ Copyright (c) 2025 Avengineers
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
ihexer-0.0.0/PKG-INFO ADDED
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.3
2
+ Name: ihexer
3
+ Version: 0.0.0
4
+ Summary: Parse, view and diff files in Intel HEX format.
5
+ License: MIT
6
+ Author: Avengineers
7
+ Author-email: karsten.guenther@kamg.de
8
+ Requires-Python: >=3.10,<3.14
9
+ Classifier: Development Status :: 2 - Pre-Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Natural Language :: English
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Dist: intelhex (>=2.3,<3.0)
21
+ Requires-Dist: py-app-dev (>=2.1.0,<3.0.0)
22
+ Requires-Dist: typer (>=0.12.3,<0.13.0)
23
+ Project-URL: Bug Tracker, https://github.com/avengineers/ihexer/issues
24
+ Project-URL: Changelog, https://github.com/avengineers/ihexer/blob/develop/CHANGELOG.md
25
+ Project-URL: Documentation, https://ihexer.readthedocs.io
26
+ Project-URL: Repository, https://github.com/avengineers/ihexer
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Intel HEX Tools
30
+
31
+ <p align="center">
32
+ <a href="https://github.com/avengineers/ihexer/actions/workflows/ci.yml?query=branch%3Adevelop">
33
+ <img src="https://img.shields.io/github/actions/workflow/status/avengineers/ihexer/ci.yml?branch=develop&label=CI&logo=github&style=flat-square" alt="CI Status" >
34
+ </a>
35
+ <a href="https://ihexer.readthedocs.io">
36
+ <img src="https://img.shields.io/readthedocs/ihexer.svg?logo=read-the-docs&logoColor=fff&style=flat-square" alt="Documentation Status">
37
+ </a>
38
+ <a href="https://codecov.io/gh/avengineers/ihexer">
39
+ <img src="https://img.shields.io/codecov/c/github/avengineers/ihexer.svg?logo=codecov&logoColor=fff&style=flat-square" alt="Test coverage percentage">
40
+ </a>
41
+ </p>
42
+ <p align="center">
43
+ <a href="https://python-poetry.org/">
44
+ <img src="https://img.shields.io/badge/packaging-poetry-299bd7?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAASCAYAAABrXO8xAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJJSURBVHgBfZLPa1NBEMe/s7tNXoxW1KJQKaUHkXhQvHgW6UHQQ09CBS/6V3hKc/AP8CqCrUcpmop3Cx48eDB4yEECjVQrlZb80CRN8t6OM/teagVxYZi38+Yz853dJbzoMV3MM8cJUcLMSUKIE8AzQ2PieZzFxEJOHMOgMQQ+dUgSAckNXhapU/NMhDSWLs1B24A8sO1xrN4NECkcAC9ASkiIJc6k5TRiUDPhnyMMdhKc+Zx19l6SgyeW76BEONY9exVQMzKExGKwwPsCzza7KGSSWRWEQhyEaDXp6ZHEr416ygbiKYOd7TEWvvcQIeusHYMJGhTwF9y7sGnSwaWyFAiyoxzqW0PM/RjghPxF2pWReAowTEXnDh0xgcLs8l2YQmOrj3N7ByiqEoH0cARs4u78WgAVkoEDIDoOi3AkcLOHU60RIg5wC4ZuTC7FaHKQm8Hq1fQuSOBvX/sodmNJSB5geaF5CPIkUeecdMxieoRO5jz9bheL6/tXjrwCyX/UYBUcjCaWHljx1xiX6z9xEjkYAzbGVnB8pvLmyXm9ep+W8CmsSHQQY77Zx1zboxAV0w7ybMhQmfqdmmw3nEp1I0Z+FGO6M8LZdoyZnuzzBdjISicKRnpxzI9fPb+0oYXsNdyi+d3h9bm9MWYHFtPeIZfLwzmFDKy1ai3p+PDls1Llz4yyFpferxjnyjJDSEy9CaCx5m2cJPerq6Xm34eTrZt3PqxYO1XOwDYZrFlH1fWnpU38Y9HRze3lj0vOujZcXKuuXm3jP+s3KbZVra7y2EAAAAAASUVORK5CYII=" alt="Poetry">
45
+ </a>
46
+ <a href="https://github.com/astral-sh/ruff">
47
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="ruff">
48
+ </a>
49
+ <a href="https://github.com/pre-commit/pre-commit">
50
+ <img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" alt="pre-commit">
51
+ </a>
52
+ </p>
53
+ <p align="center">
54
+ <a href="https://pypi.org/project/ihexer/">
55
+ <img src="https://img.shields.io/pypi/v/ihexer.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
56
+ </a>
57
+ <img src="https://img.shields.io/pypi/pyversions/ihexer.svg?style=flat-square&logo=python&amp;logoColor=fff" alt="Supported Python versions">
58
+ <img src="https://img.shields.io/pypi/l/ihexer.svg?style=flat-square" alt="License">
59
+ </p>
60
+
61
+ Parse, view and diff files in Intel HEX format.
62
+
63
+ ## Installation
64
+
65
+ Install this via pip (or your favourite package manager):
66
+
67
+ `pip install ihexer`
68
+
69
+ ## Start developing
70
+
71
+ The project uses Poetry for dependencies management and packaging.
72
+ Run the `bootstrap.ps1` script to install Python and create the virtual environment. (This is only working on windows machines!)
73
+
74
+ ```powershell
75
+ .\bootstrap.ps1
76
+ ```
77
+
78
+ This will also generate a `poetry.lock` file, you should track this file in version control.
79
+
80
+ If you want to customize the bootstrapping process please create a bootstrap.json file according to the [bootstrap documentation](https://github.com/avengineers/bootstrap?tab=readme-ov-file#configuration).
81
+
82
+ To execute the test suite, call pytest inside Poetry's virtual environment via `poetry run`:
83
+
84
+ ```shell
85
+ .venv/Scripts/poetry run pytest
86
+ ```
87
+
88
+ Check out the Poetry documentation for more information on the available commands.
89
+
90
+ For those using [VS Code](https://code.visualstudio.com/) there are tasks defined for the most common commands:
91
+
92
+ - bootstrap
93
+ - install dependencies
94
+ - run tests
95
+ - run all checks configured for pre-commit
96
+ - generate documentation
97
+
98
+ See the `.vscode/tasks.json` for more details.
99
+
100
+ ## Committing changes
101
+
102
+ This repository uses [commitlint](https://github.com/conventional-changelog/commitlint) for checking if the commit message meets the [conventional commit format](https://www.conventionalcommits.org/en).
103
+
104
+ ## Release
105
+
106
+ This repository uses [semantic release](https://python-semantic-release.readthedocs.io/en/latest/) to automate versioning the Python projects.
107
+ The package version will be automatically updated when the `develop` branch is built.
108
+
109
+ ## Contributors ✨
110
+
111
+ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
112
+
113
+ <!-- prettier-ignore-start -->
114
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
115
+ <!-- markdownlint-disable -->
116
+ <!-- markdownlint-enable -->
117
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
118
+ <!-- prettier-ignore-end -->
119
+
120
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
121
+
122
+ ## Credits
123
+
124
+ This package was created with
125
+ [Copier](https://copier.readthedocs.io/) and the
126
+ [cuinixam/pypackage-template](https://github.com/cuinixam/pypackage-template)
127
+ project template.
128
+
ihexer-0.0.0/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # Intel HEX Tools
2
+
3
+ <p align="center">
4
+ <a href="https://github.com/avengineers/ihexer/actions/workflows/ci.yml?query=branch%3Adevelop">
5
+ <img src="https://img.shields.io/github/actions/workflow/status/avengineers/ihexer/ci.yml?branch=develop&label=CI&logo=github&style=flat-square" alt="CI Status" >
6
+ </a>
7
+ <a href="https://ihexer.readthedocs.io">
8
+ <img src="https://img.shields.io/readthedocs/ihexer.svg?logo=read-the-docs&logoColor=fff&style=flat-square" alt="Documentation Status">
9
+ </a>
10
+ <a href="https://codecov.io/gh/avengineers/ihexer">
11
+ <img src="https://img.shields.io/codecov/c/github/avengineers/ihexer.svg?logo=codecov&logoColor=fff&style=flat-square" alt="Test coverage percentage">
12
+ </a>
13
+ </p>
14
+ <p align="center">
15
+ <a href="https://python-poetry.org/">
16
+ <img src="https://img.shields.io/badge/packaging-poetry-299bd7?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAASCAYAAABrXO8xAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJJSURBVHgBfZLPa1NBEMe/s7tNXoxW1KJQKaUHkXhQvHgW6UHQQ09CBS/6V3hKc/AP8CqCrUcpmop3Cx48eDB4yEECjVQrlZb80CRN8t6OM/teagVxYZi38+Yz853dJbzoMV3MM8cJUcLMSUKIE8AzQ2PieZzFxEJOHMOgMQQ+dUgSAckNXhapU/NMhDSWLs1B24A8sO1xrN4NECkcAC9ASkiIJc6k5TRiUDPhnyMMdhKc+Zx19l6SgyeW76BEONY9exVQMzKExGKwwPsCzza7KGSSWRWEQhyEaDXp6ZHEr416ygbiKYOd7TEWvvcQIeusHYMJGhTwF9y7sGnSwaWyFAiyoxzqW0PM/RjghPxF2pWReAowTEXnDh0xgcLs8l2YQmOrj3N7ByiqEoH0cARs4u78WgAVkoEDIDoOi3AkcLOHU60RIg5wC4ZuTC7FaHKQm8Hq1fQuSOBvX/sodmNJSB5geaF5CPIkUeecdMxieoRO5jz9bheL6/tXjrwCyX/UYBUcjCaWHljx1xiX6z9xEjkYAzbGVnB8pvLmyXm9ep+W8CmsSHQQY77Zx1zboxAV0w7ybMhQmfqdmmw3nEp1I0Z+FGO6M8LZdoyZnuzzBdjISicKRnpxzI9fPb+0oYXsNdyi+d3h9bm9MWYHFtPeIZfLwzmFDKy1ai3p+PDls1Llz4yyFpferxjnyjJDSEy9CaCx5m2cJPerq6Xm34eTrZt3PqxYO1XOwDYZrFlH1fWnpU38Y9HRze3lj0vOujZcXKuuXm3jP+s3KbZVra7y2EAAAAAASUVORK5CYII=" alt="Poetry">
17
+ </a>
18
+ <a href="https://github.com/astral-sh/ruff">
19
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="ruff">
20
+ </a>
21
+ <a href="https://github.com/pre-commit/pre-commit">
22
+ <img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" alt="pre-commit">
23
+ </a>
24
+ </p>
25
+ <p align="center">
26
+ <a href="https://pypi.org/project/ihexer/">
27
+ <img src="https://img.shields.io/pypi/v/ihexer.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
28
+ </a>
29
+ <img src="https://img.shields.io/pypi/pyversions/ihexer.svg?style=flat-square&logo=python&amp;logoColor=fff" alt="Supported Python versions">
30
+ <img src="https://img.shields.io/pypi/l/ihexer.svg?style=flat-square" alt="License">
31
+ </p>
32
+
33
+ Parse, view and diff files in Intel HEX format.
34
+
35
+ ## Installation
36
+
37
+ Install this via pip (or your favourite package manager):
38
+
39
+ `pip install ihexer`
40
+
41
+ ## Start developing
42
+
43
+ The project uses Poetry for dependencies management and packaging.
44
+ Run the `bootstrap.ps1` script to install Python and create the virtual environment. (This is only working on windows machines!)
45
+
46
+ ```powershell
47
+ .\bootstrap.ps1
48
+ ```
49
+
50
+ This will also generate a `poetry.lock` file, you should track this file in version control.
51
+
52
+ If you want to customize the bootstrapping process please create a bootstrap.json file according to the [bootstrap documentation](https://github.com/avengineers/bootstrap?tab=readme-ov-file#configuration).
53
+
54
+ To execute the test suite, call pytest inside Poetry's virtual environment via `poetry run`:
55
+
56
+ ```shell
57
+ .venv/Scripts/poetry run pytest
58
+ ```
59
+
60
+ Check out the Poetry documentation for more information on the available commands.
61
+
62
+ For those using [VS Code](https://code.visualstudio.com/) there are tasks defined for the most common commands:
63
+
64
+ - bootstrap
65
+ - install dependencies
66
+ - run tests
67
+ - run all checks configured for pre-commit
68
+ - generate documentation
69
+
70
+ See the `.vscode/tasks.json` for more details.
71
+
72
+ ## Committing changes
73
+
74
+ This repository uses [commitlint](https://github.com/conventional-changelog/commitlint) for checking if the commit message meets the [conventional commit format](https://www.conventionalcommits.org/en).
75
+
76
+ ## Release
77
+
78
+ This repository uses [semantic release](https://python-semantic-release.readthedocs.io/en/latest/) to automate versioning the Python projects.
79
+ The package version will be automatically updated when the `develop` branch is built.
80
+
81
+ ## Contributors ✨
82
+
83
+ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
84
+
85
+ <!-- prettier-ignore-start -->
86
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
87
+ <!-- markdownlint-disable -->
88
+ <!-- markdownlint-enable -->
89
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
90
+ <!-- prettier-ignore-end -->
91
+
92
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
93
+
94
+ ## Credits
95
+
96
+ This package was created with
97
+ [Copier](https://copier.readthedocs.io/) and the
98
+ [cuinixam/pypackage-template](https://github.com/cuinixam/pypackage-template)
99
+ project template.
@@ -0,0 +1,160 @@
1
+ [tool.poetry]
2
+ name = "ihexer"
3
+ version = "0.0.0"
4
+ description = "Parse, view and diff files in Intel HEX format."
5
+ authors = ["Avengineers <karsten.guenther@kamg.de>"]
6
+ license = "MIT"
7
+ readme = "README.md"
8
+ repository = "https://github.com/avengineers/ihexer"
9
+ documentation = "https://ihexer.readthedocs.io"
10
+ classifiers = [
11
+ "Development Status :: 2 - Pre-Alpha",
12
+ "Intended Audience :: Developers",
13
+ "Natural Language :: English",
14
+ "Operating System :: OS Independent",
15
+ "Topic :: Software Development :: Libraries",
16
+ ]
17
+ packages = [
18
+ { include = "ihexer", from = "src" },
19
+ ]
20
+
21
+ [tool.poetry.urls]
22
+ "Bug Tracker" = "https://github.com/avengineers/ihexer/issues"
23
+ "Changelog" = "https://github.com/avengineers/ihexer/blob/develop/CHANGELOG.md"
24
+
25
+ [tool.poetry.dependencies]
26
+ python = ">=3.10,<3.14"
27
+ py-app-dev = "^2.1.0"
28
+ typer = "^0.12.3"
29
+ intelhex = "^2.3"
30
+
31
+ [tool.poetry.group.dev.dependencies]
32
+ pytest = "^7.0"
33
+ pytest-cov = "^4.0"
34
+ pre-commit = "^3.1.1"
35
+ ruff = "^0.3.0"
36
+ pypeline-runner = "^0.3"
37
+ python-semantic-release = "^9.1"
38
+
39
+ [tool.poetry.group.docs.dependencies]
40
+ myst-parser = ">=0.16"
41
+ sphinx = "^7.3.5"
42
+ sphinxcontrib-mermaid = "^0.9.2"
43
+ mlx-traceability = "^10.0.0"
44
+ sphinx-copybutton = "^0.5.2"
45
+ sphinx-new-tab-link = "^0.4.0"
46
+ sphinx-book-theme = "^1.1.2"
47
+ sphinx-design = "^0.5.0"
48
+
49
+ [tool.semantic_release]
50
+ version_toml = ["pyproject.toml:tool.poetry.version"]
51
+ version_variables = [
52
+ "src/ihexer/__init__.py:__version__",
53
+ "docs/conf.py:release",
54
+ ]
55
+ build_command = "pip install poetry && poetry build"
56
+
57
+ [tool.semantic_release.changelog]
58
+ exclude_commit_patterns = [
59
+ "chore*",
60
+ "ci*",
61
+ ]
62
+
63
+ [tool.semantic_release.changelog.environment]
64
+ keep_trailing_newline = true
65
+
66
+ [tool.semantic_release.branches.main]
67
+ match = "develop"
68
+
69
+ [tool.semantic_release.branches.noop]
70
+ match = "(?!develop$)"
71
+ prerelease = true
72
+
73
+ [tool.pytest.ini_options]
74
+ addopts = "-v -Wdefault --cov=ihexer --cov-report=term-missing:skip-covered"
75
+ pythonpath = ["src"]
76
+
77
+ [tool.coverage.run]
78
+ branch = true
79
+
80
+ [tool.coverage.report]
81
+ exclude_lines = [
82
+ "pragma: no cover",
83
+ "@overload",
84
+ "if TYPE_CHECKING",
85
+ "raise NotImplementedError",
86
+ 'if __name__ == "__main__":',
87
+ ]
88
+
89
+ [tool.ruff]
90
+ target-version = "py38"
91
+ line-length = 180
92
+ lint.ignore = [
93
+ "D203", # 1 blank line required before class docstring
94
+ "D212", # Multi-line docstring summary should start at the first line
95
+ "D100", # Missing docstring in public module
96
+ "D101", # Missing docstring in public class
97
+ "D102", # Missing docstring in public method
98
+ "D103", # Missing docstring in public function
99
+ "D104", # Missing docstring in public package
100
+ "D107", # Missing docstring in `__init__`
101
+ "D401", # First line of docstring should be in imperative mood
102
+ ]
103
+ lint.select = [
104
+ "B", # flake8-bugbear
105
+ "D", # flake8-docstrings
106
+ "C4", # flake8-comprehensions
107
+ "S", # flake8-bandit
108
+ "F", # pyflake
109
+ "E", # pycodestyle
110
+ "W", # pycodestyle
111
+ "UP", # pyupgrade
112
+ "I", # isort
113
+ "RUF", # ruff specific
114
+ ]
115
+ exclude = [
116
+ "bootstrap.py"
117
+ ]
118
+
119
+ [tool.ruff.lint.per-file-ignores]
120
+ "tests/**/*" = [
121
+ "D100",
122
+ "D101",
123
+ "D102",
124
+ "D103",
125
+ "D104",
126
+ "S101",
127
+ ]
128
+ "setup.py" = ["D100"]
129
+ "conftest.py" = ["D100"]
130
+ "docs/conf.py" = ["D100"]
131
+
132
+ [tool.ruff.lint.isort]
133
+ known-first-party = ["ihexer", "tests"]
134
+
135
+ [tool.mypy]
136
+ check_untyped_defs = true
137
+ disallow_any_generics = true
138
+ disallow_incomplete_defs = true
139
+ disallow_untyped_defs = true
140
+ mypy_path = "src/"
141
+ no_implicit_optional = true
142
+ show_error_codes = true
143
+ warn_unreachable = true
144
+ warn_unused_ignores = true
145
+ exclude = [
146
+ 'docs/.*',
147
+ 'setup.py',
148
+ ]
149
+
150
+ [[tool.mypy.overrides]]
151
+ module = "tests.*"
152
+ allow_untyped_defs = true
153
+
154
+ [[tool.mypy.overrides]]
155
+ module = "docs.*"
156
+ ignore_errors = true
157
+
158
+ [build-system]
159
+ requires = ["poetry-core>=1.0.0"]
160
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1 @@
1
+ __version__ = "0.0.0"
@@ -0,0 +1,12 @@
1
+ """
2
+ Used to run ihexer from the command line when run from this repository.
3
+
4
+ This is required because ihexer module is not visible when running from the repository.
5
+ """
6
+
7
+ import runpy
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ sys.path.insert(0, Path(__file__).parent.parent.absolute().as_posix())
12
+ runpy.run_module("ihexer.main", run_name="__main__")
@@ -0,0 +1,193 @@
1
+ import difflib
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+ from typing import List
5
+
6
+ from intelhex import IntelHex as _IntelHex
7
+ from py_app_dev.core.exceptions import UserNotificationException
8
+
9
+
10
+ @dataclass
11
+ class IntelHexSegment:
12
+ start: int
13
+ end: int
14
+ data: bytes
15
+
16
+ @property
17
+ def length(self) -> int:
18
+ return self.end - self.start + 1
19
+
20
+ def __post_init__(self) -> None:
21
+ """Make sure the segment can have a size."""
22
+ if self.end < self.start:
23
+ raise ValueError(f"Segments length has negative size: end {self.end} < start {self.start}.")
24
+
25
+ def __str__(self) -> str:
26
+ """Only print the address range."""
27
+ return f"{hex(self.start)}-{hex(self.end)}: {self.length} bytes"
28
+
29
+
30
+ @dataclass
31
+ class IntelHex:
32
+ start_address: int
33
+ end_address: int
34
+ segments: List[IntelHexSegment]
35
+
36
+ def get_content(self) -> str:
37
+ """Return the hex content of all segments as a single long string."""
38
+ hex_content = []
39
+ for segment in self.segments:
40
+ hex_content.append(segment.data.hex().upper())
41
+ return "".join(hex_content)
42
+
43
+ def __str__(self) -> str:
44
+ """Return the content when the object is printed."""
45
+ return self.get_content()
46
+
47
+ def write_hex_file(self, output_file: Path) -> None:
48
+ """Write the IntelHex content back to a hex file."""
49
+ # Create a new _IntelHex object to store the data
50
+ intel_hex = _IntelHex()
51
+
52
+ # For each segment, add its data to the _IntelHex object
53
+ for segment in self.segments:
54
+ intel_hex.puts(segment.start, segment.data)
55
+
56
+ # Write the data to the specified file
57
+ intel_hex.write_hex_file(output_file.as_posix())
58
+
59
+
60
+ class IntelHexParser:
61
+ def __init__(self, file: Path, bytes_swap: bool = False, word_size: int = 4) -> None:
62
+ self.file = file
63
+ self.bytes_swap = bytes_swap
64
+ self.word_size = word_size
65
+
66
+ def parse(self) -> IntelHex:
67
+ segments = self._read_segments(self._parse_content())
68
+ return IntelHex(
69
+ min([segment.start for segment in segments]),
70
+ max([segment.end for segment in segments]),
71
+ segments,
72
+ )
73
+
74
+ def _parse_content(self) -> _IntelHex:
75
+ _content = _IntelHex()
76
+ format = self.file.suffix.lower().replace(".", "")
77
+ try:
78
+ _content.loadfile(self.file.as_posix(), format)
79
+ except ValueError as e:
80
+ raise UserNotificationException(f"Could not load '{self.file}'. Unsupported format {format}") from e
81
+ return _content
82
+
83
+ def _read_segments(self, content: _IntelHex) -> List[IntelHexSegment]:
84
+ segments: List[IntelHexSegment] = []
85
+ _segments = content.segments()
86
+ if _segments:
87
+ for segment_addresses_list in _segments:
88
+ start_address = segment_addresses_list[0]
89
+ # For some reason IntelHex sets the end address to the next available address. Bug?
90
+ end_address = segment_addresses_list[1] - 1
91
+ data: bytes = content.gets(start_address, end_address - start_address + 1)
92
+ if self.bytes_swap:
93
+ data = self.swap_bytes(data, self.word_size)
94
+ segments.append(
95
+ IntelHexSegment(
96
+ start_address,
97
+ end_address,
98
+ data,
99
+ )
100
+ )
101
+ return segments
102
+
103
+ @staticmethod
104
+ def swap_bytes(data: bytes, word_size: int) -> bytes:
105
+ if len(data) % word_size != 0:
106
+ raise UserNotificationException(f"Data length {len(data)} is not a multiple of word size {word_size}.")
107
+ swapped_data = bytearray()
108
+ for i in range(0, len(data), word_size):
109
+ swapped_data.extend(reversed(data[i : i + word_size]))
110
+ return bytes(swapped_data)
111
+
112
+
113
+ class IntelHexPrinter:
114
+ def __init__(self, content: IntelHex) -> None:
115
+ self.content = content
116
+
117
+ @staticmethod
118
+ def stringify_segment(segment: IntelHexSegment) -> List[str]:
119
+ output_lines = []
120
+ # Calculate the alignment offset from the nearest lower multiple of 16
121
+ alignment_offset = segment.start % 16
122
+
123
+ # Adjust the start address to be aligned to 16 bytes
124
+ aligned_start_address = segment.start - alignment_offset
125
+
126
+ # Calculate the number of lines, ensuring at least one line
127
+ # Include the offset in the calculation for total data length
128
+ num_lines = (len(segment.data) + alignment_offset + 15) // 16 if segment.data else 1
129
+
130
+ for i in range(num_lines):
131
+ start_idx = i * 16 - alignment_offset
132
+ end_idx = start_idx + 16
133
+ current_data_chunk = segment.data[max(0, start_idx) : end_idx]
134
+
135
+ address_str = f"{aligned_start_address + i*16:08X}"
136
+
137
+ # Prepare the data bytes, filling missing bytes with '--' at the beginning due to alignment
138
+ data_strs = ["--"] * max(0, -start_idx) # Fill with '--' if start_idx is negative
139
+ data_strs += [f"{byte:02X}" for byte in current_data_chunk]
140
+ missing_bytes = 16 - len(data_strs)
141
+ data_strs.extend(["--"] * missing_bytes)
142
+
143
+ data_str = " ".join(data_strs)
144
+ segment_str = f"{address_str}: {data_str}"
145
+ output_lines.append(segment_str)
146
+ return output_lines
147
+
148
+ @staticmethod
149
+ def stringify_segments(segments: List[IntelHexSegment]) -> List[str]:
150
+ output_lines = []
151
+ for segment in segments:
152
+ output_lines.extend(IntelHexPrinter.stringify_segment(segment))
153
+ return output_lines
154
+
155
+ @staticmethod
156
+ def stringify_short_info(segments: List[IntelHexSegment]) -> List[str]:
157
+ delimiter = "-" * 57
158
+ output_lines = [delimiter, "SEGMENTS:"]
159
+ for segment in segments:
160
+ output_lines.append(str(segment))
161
+ output_lines.append(delimiter)
162
+ return output_lines
163
+
164
+ def to_string(self, with_short_info: bool = True) -> str:
165
+ content = []
166
+ if with_short_info:
167
+ content.extend(IntelHexPrinter.stringify_short_info(self.content.segments))
168
+ content.extend(IntelHexPrinter.stringify_segments(self.content.segments))
169
+ return "\n".join(content)
170
+
171
+
172
+ class IntelHexDiff:
173
+ def __init__(self, first: Path, second: Path) -> None:
174
+ self.first_file = first
175
+ self.second_file = second
176
+
177
+ def generate_html(self) -> str:
178
+ first_list = IntelHexPrinter.stringify_segments(IntelHexParser(self.first_file).parse().segments)
179
+ second_list = IntelHexPrinter.stringify_segments(IntelHexParser(self.second_file).parse().segments)
180
+
181
+ html_diff = difflib.HtmlDiff()
182
+
183
+ return html_diff.make_file(
184
+ first_list,
185
+ second_list,
186
+ fromdesc=f"{self.first_file}",
187
+ todesc=f"{self.second_file}",
188
+ context=True,
189
+ numlines=5,
190
+ )
191
+
192
+ def generate(self, out_file: Path) -> None:
193
+ out_file.write_text(self.generate_html())
@@ -0,0 +1,80 @@
1
+ import sys
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ import typer
6
+ from py_app_dev.core.exceptions import UserNotificationException
7
+ from py_app_dev.core.logging import logger, setup_logger, time_it
8
+
9
+ from ihexer import __version__
10
+ from ihexer.ihexer import IntelHexDiff, IntelHexParser, IntelHexPrinter
11
+
12
+ package_name = "ihexer"
13
+
14
+ app = typer.Typer(name=package_name, help="Intel HEX utils.", no_args_is_help=True)
15
+
16
+
17
+ @app.callback(invoke_without_command=True)
18
+ def version(
19
+ version: bool = typer.Option(None, "--version", "-v", is_eager=True, help="Show version and exit."),
20
+ ) -> None:
21
+ if version:
22
+ typer.echo(f"{package_name} {__version__}")
23
+ raise typer.Exit()
24
+
25
+
26
+ @app.command()
27
+ @time_it("view")
28
+ def view(
29
+ file: Path = typer.Option(help="Input hex/bin file."), # noqa: B008
30
+ dump_to: Optional[Path] = typer.Option(None, help="Output file to dump the hex content."), # noqa: B008
31
+ short_info: bool = typer.Option(True, help="Dump hex file short info before the content."),
32
+ ) -> None:
33
+ intel_hex = IntelHexParser(file).parse()
34
+ print(IntelHexPrinter(intel_hex).to_string(short_info))
35
+ if dump_to:
36
+ dump_to.write_text(IntelHexPrinter(intel_hex).to_string(short_info))
37
+
38
+
39
+ @app.command()
40
+ @time_it("diff")
41
+ def diff(
42
+ first: Path = typer.Option(help="First input hex/bin file."), # noqa: B008
43
+ second: Path = typer.Option(help="Second input hex/bin file."), # noqa: B008
44
+ out_file: Path = typer.Option(help="Output file to write the html diff report."), # noqa: B008
45
+ ) -> None:
46
+ IntelHexDiff(first, second).generate(out_file)
47
+
48
+
49
+ @app.command()
50
+ @time_it("info")
51
+ def info(
52
+ file: Path = typer.Option(help="Input hex/bin file."), # noqa: B008
53
+ ) -> None:
54
+ intel_hex = IntelHexParser(file).parse()
55
+ for line in IntelHexPrinter.stringify_short_info(intel_hex.segments):
56
+ print(line)
57
+
58
+
59
+ @app.command()
60
+ @time_it("swap")
61
+ def swap(
62
+ file: Path = typer.Option(help="Input hex/bin file."), # noqa: B008
63
+ out_file: Optional[Path] = typer.Option(None, help="Output file to write the swapped hex content."), # noqa: B008
64
+ word_size: int = typer.Option(4, help="Word size in bytes."),
65
+ ) -> None:
66
+ IntelHexParser(file, bytes_swap=True, word_size=word_size).parse().write_hex_file(out_file or file)
67
+
68
+
69
+ def main() -> int:
70
+ try:
71
+ setup_logger()
72
+ app()
73
+ return 0
74
+ except UserNotificationException as e:
75
+ logger.error(f"{e}")
76
+ return 1
77
+
78
+
79
+ if __name__ == "__main__":
80
+ sys.exit(main())
File without changes