[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: raulgotor\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\npolar: # Replace with a single Polar username\nbuy_me_a_coffee: # Replace with a single Buy Me a Coffee username\nthanks_dev: # Replace with a single thanks.dev username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: Pylint\n\non: [push]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.8\", \"3.9\", \"3.10\"]\n    steps:\n    - uses: actions/checkout@v3\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v3\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install pylint\n    - name: Analysing the code with pylint\n      run: |\n        pylint $(git ls-files '*.py') --disable=C0114,R0902,C0116 --fail-under=9\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# IDE\n.idea\n\n# OS\n.DS_Store\n\n# Artifacts\n*.svg\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to\n[Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n### Added\n* `auto` property to `hide-*` attributes so LinkerScope can explicitly decide whether the attribute should be hidden due to overlapping issues \n* `hidden` property to sections to allow hiding one section while still computing its properties \n\n## [0.3.1] - 2024-02-03\n\n### Added\n* `--convert` flag to simply convert `.map` files to `.yaml` files\n\n## [0.3.0] - 2024-02-03\n\n### Added\n* Title feature and property (`title`) for the different areas\n* `hide-name`, `hide-address` and `hide-size` style properties to hide specific visual elements\n* `flags` can be specified at map file as well\n* `size` property at root level to modify the document size\n* `labels` property can go on the left side as well\n* Friendly name field (`name`) for sections at the yaml map file to be used instead of ID\n\n### Fixed\n* Design issue that was hindering creating linked sections if the area contained breaks\n* Bug that repeated the title of the area if the area contained breaks\n* Breaks on areas that had empty regions would not perform correctly\n\n## [0.2.0] - 2023-12-14\n\n### Added\n* Style overriding by section: each section can have its own style\n* Added `links/sections`, which links a section or group of between main and secondary area\n* `flags` property for each section\n* `opacity` style property that controls the background opacity of a linked section\n* `background` style property that controls the document's background\n* `grows-up` and `grows-down` flags for sections that draw an arrow indicating the growth direction of the section\n* `growth-arrow-weight`, `-stroke-color` and `-fill-color` style properties for the sections growth arrows\n* Method at `Style` class to easily override properties from another object: `override_properties_from`\n* Method at `Style` class to get a default initialized object: `get_default`\n* Property names in yaml also accept `-` instead of underscore\n* `style` property to `links`, `area`, and `area/sections`\n* flags are specified per section at `area/sections/`\n* custom labels at specific memory addresses\n\n### Changed\n* Naming of the memory domain: Area instead of Map\n* Refactored initialization of `AreaView` to pass parameters via `kwargs`\n* Refactored out parameter `dwg` at `map_drawer.py`\n* `size-x` and `size-y` moved to `[size]`, `x` and `y` to `[pos]`, `addresses` to `[range]`\n* overridden style for areas is constructed before class declaration, and not before drawing\n\n## [0.1.0] - 2023-11-24\n\n* Initial release\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Raúl Gotor\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# LinkerScope\n\n## Project summary\n\nLinkerScope is a memory map diagram generator. It can be fed either with a GNU Linker map file or with a custom `yaml` file\nand generate beautiful and detailed diagrams of the different areas and sections such as the one below.\n\n![Example of_STM32F103 Memory Map generated with LinkerScope](docs/assets/stm32f103_map.svg)\n\n<a href=\"https://ko-fi.com/K3K010I5L1\">\n  <img src=\".github/kofi_button_blue.png\" alt=\"ko-fi\" width=\"230\"/>\n</a>\n\n## Installing LinkerScope\n\nOptionally create and activate an environment for LinkerScope:\n\n```bash\npython3 -m venv venv\nsource env/bin/activate\n```\n\nInstall the needed requirements by executing:\n\n```bash\npip3 install -r requirements.txt\n```\n\n## Usage\n\n### Execution\n\nLinkerScope needs a file with memory information for basic functionality:\nthat could be either GNU Linker map files (`.map`) or custom structured YAML file (`.yaml`).\n\n```bash\n./linkerscope.py linker.map [<options>]\n```\n\nThis will output memory map diagram to a default `map.svg` file. \nWhile this enables a quick generation of memory map, one might want to carefully tune the\ndifferent properties of the diagram such as visual and style properties, memory regions, names, links, ...\n\nFor that, a [configuration file](#creating-a-configuration-file) with the desired characteristics has to be specified:\n\n```bash\n./linkerscope.py linker.map --config config.yaml --output map.svg\n```\n\nwhere:\n- First parameter specifies the path to the input file, where LinkerScope should get the data to represent from. It can come from a GNU Linker map file `.map` or from an already parsed or hand-crafted `.yaml` file. Check [Manually crafting input file](#Manually crafting input file) section for learning how to do this.\n- `-c, --config` [OPTIONAL] specifies the path to the configuration file. This file contains all the custom information to tell LinkerScope what to and how to draw the memory maps. While it is optional, the default parameters will most likely not apply to a given use case.\n- `-o, --output` [OPTIONAL] specifies the path to the output file, which will be a newly generated SVG.\n- `--convert` [OPTIONAL] tells LinkerScope to perform a conversion from a `.map` file to `.yaml` file containing memory information. After conversion, proqram will quit.\n\n\n### Input files\n\nLinkerScope can use two types of input files: GNU linker map files (`.map`) or custom defined yaml files (`.yaml`).\n\n#### Using .map files\n\nUnder the hood, LinkerScope will convert `.map` files to custom `.yaml` ones, and will work from there.\nSince this operation is time-consuming and makes no sense to do it multiple times, two strategies can be\nperformed when using `.map` files:\n- Convert `.map` files to `.yaml` file and then use the `.yaml` file as an input to LinkerScope\n  > This is specially useful if you plan to execute LinkerScope multiple times, since this conversion is time-consuming. Therefore better doing the conversion step once, right? Execute the example below:\n  > ```shell\n  >  # Conversion step\n  > ./linkerscope.py examples/sample_map.map --convert \n  > \n  >  # Map diagram generation. You can execute multiple times without having to do the conversion again\n  > ./linkerscope.py map.yaml -c examples/sample_config.yaml -o sample_map.svg \n  >```\n- Directly use the `.map` files to output a memory map diagram.\n  > Use this strategy when you already have a configuration file, and you know that LinkerScope will produce the expected result. Execute the example below:\n  > ```shell\n  > ./linkerscope.py examples/sample_map.map -c examples/sample_config.yaml -o sample_map.svg \n  > ```\n\n#### Manually crafted memory map files\n\nCustom memory map files can be manually crafted and can run from a couple of memory sections up to very complex memory schemes with hundreds of sections.\nNormally you would do that when representing simple memory maps.\n\nFor making a memory map file, one has to specify at least a single section. Each section must include\nan `id`, an `address` and a `size`.\n\nWhile these three are needed, there are other possible attributes that are optional:\n\n- `name`: Friendly text name that would be used instead of the `id`\n- `type`: Section type, which can be used for different purposes. Current possibilities are `section` (default) and `area`.\n\nThe input file should contain the `map` keyword whose value is an array of sections. Below an example\nof how an input file should look like:\n\n```yaml\n- address: 0x80045D4\n  size:    0x0000F00\n  id:      .rodata\n  name:    Read Only Data\n  type:    area\n- address: 0x8002C3C\n  id:      SysTick_Handler\n  size:    0x0000008\n- ...\n```\n\nIn order to use this file, invoke LinkerScope and specify the yaml map file as input:\n\n```bash\n./linkerscope.py memory_map.yaml -o memory_map.svg -c config.yaml\n```\n\n### Creating a configuration file\n\nThe configuration file is a `.yaml` file containing all the required information to tell\nLinkerScope what and how to draw the memory map. All information there is optional, including the\nfile itself. If this information is not provided, the default values will be used.\n\nNormally, a configuration file contains **areas**, **links** and **style** information.\n\n**Areas** provides a list of memory areas to be displayed, with information regarding its position\nand size on the map, memory range to include or specific drawing style.\nAdditionally, it can contain a **sections** sub-property where specific sections can be added to\nmodify desired properties\n\n**Links** provides a simple way of graphically relating same memory addresses across different areas\n\n**Style** defines the parent drawing style properties that will be used at the document.\nAdditionally, each area can override specific style properties at its style section.\nLastly, sections can override parent (area) style as well\n\n```yaml\nstyle:\n  background: '#99B898'\n  stroke: 'black'\n  # ...\n\nareas:\n- area:\n    style:\n    # ...\n    title: Register Map\n    range: [0x0, 0x100000000]\n    sections:\n      - names: [USART1, USART2]\n        style:\n          hide-name: true\n    # ...\n- area:\n    # ...\n\nlinks:\n  addresses: [0x80045d4]\n  sections: [__malloc_av_, [TIM2, TIM3]]\n\n```\n\n#### Areas\n\nThe diagram can have one or multiple areas. When multiple areas are declared,\nfirst area has a special status since all links will start on it and go to the corresponding sections on the other areas\nThe areas are declared at root level under `areas`. Then each area must use the key `area`.\nAreas can be placed at any position in the document, and some of its properties can be tuned.elements such as labels, memory addresses,\nFor instance, some elements such as the name / id, size, address, can be show or hidden, labels at\nspecific memory regions can be configured, sections can be marked as break sections, and the \nvisual style of the area and its sections can be modified independently from the others.\n\nFor each area, the following characteristics of an area can be defined:\n- `title`:  **[Optional, none]**\n  - The title of the area, which will appear on top of it\n- `pos`:  **[Optional, (50, 50)]** **[x, y]**\n  - absolute position  of the area's top-left corner in pixels\n- `size`:  **[Optional, (300, 600)]** **[width, height]**\n  - area size in pixels\n- `range`:  **[Optional, (0, no limit)]** **[min, max]**\n  - range of addresses that will be taken into account in this area. \n- `start`: **[start, end]** force area to start in to a given address\n- `section-size`: **[Optional, (0, no limit)]** **[min, max]**\n  - size range of the sections that should be shown. Exclude others.\n- `style`:  **[Optional, default: parent style]**\n  - specific style for current area. See [Styles](####Styles) section.\n- `sections`: **[Optional, none]**\n  - specify or modify a section or group of sections property such as `style`, `flags`,...\n    - `names`:\n      - list of one or more sections to modify with the parameters below\n    - `flags`: **[Optional, none]**\n      - flags to append to the specified section/s. See [Flags](#### Section flags) section.\n    - `style`: **[Optional, parent style]**\n      - style properties to or modify to the specified section/s\n- `labels`: **[Optional, none]**\n  - Add text labels to specific memory positions of the current area\n    - `address`:\n      - Memory address where the label should be placed\n    - `text`:\n      - Text to display for this label\n    - `length`: **[Optional, none]**\n      - length of the label line in pixels\n    - `directions`: **[Optional, none]**\n      - direction or list of directions for the arrow head. Possible values are none, `in`, `out` or both.\n    - `side`: **[Optional, `right`]**\n      - Area side at which the label should be placed\n    - `style`: **[Optional, parent style]**\n      - style properties to or modify to the specified section/s\n\nBelow an example of area definition:\n\n```yaml\nareas:\n- area:\n    title: 'Full Memory Map'\n    pos: [30, 50]\n    size: [300, 900]\n    range: [0x0, 0x100000000]\n    section-size: [0x02F00000]\n    style:\n      fill: *pastel_green\n    sections:\n    - names: [ External Device ]\n      flags: grows-up\n      style:\n        hide-size: true\n```\n#### Labels\n\nLabels can bind a text string with a specific memory position. This property falls inside `area`. \nAn extract from the `labels` example can be found below:\n\n```yaml\nareas:\n - area:\n    labels:\n      - address: 0xe8043800\n        text: In direction\n        length: 150\n        directions: in\n        style:\n          stroke-dasharray: '5,1,3,1,3'\n     # ...\n```\n\n![Example of different labels](docs/assets/labels_map.svg)\n\n> The example can be executed at the root folder by running:\n>  ```bash\n>  ./linkerscope.py examples/labels_map.yaml -c examples/labels_config.yaml\n>  ```\n\n#### Section flags\n\nSection flags allows flagging specified sections with special properties.\nThese properties are `hidden`, `break`, `grows-up` and `grows-down`.\n\nFlags are listed under property `flags` and can be specified both at the map files under each section\n\n```yaml\n# flags can be defined at map.yaml files under each section\nmap:\n- address: 0x20000000\n  id: stack\n  # ...\n  flags: grows-down, break\n```\n\nor at the configuration files, with the possibility to specify multiple sections at the same time:\n\n```yaml\n# flags can be defined at config.yaml files under each section or group of sections\nareas:\n- area:\n  # ...\n  sections:\n  - names: [ROM Table, TPIU]\n    flags: break\n```\n\n##### `hidden`\n\nThis flag marks a section or sections to be hidden. Its properties are still computed, the space\nthat occupies in the graph is still accounted for, but it is not shown. This could be useful, for\ninstance, to remove irrelevant sections while to some other more important. Another quite useful\nuse case is doing side by side memory maps:\n\n![Example for side by side maps](docs/assets/side_by_side_map.svg)\n\n> The example can be executed at the root folder by running:\n>  ```bash\n>  ./linkerscope.py examples/side_by_side_map.yaml  -c examples/side_by_side_config.yaml\n>  ```\n\n##### Growths\nThese flags specify the section as growing section, for instance, if the section is meant to grow into one direction, such as the stack.\nWhen flagging a section with `grows-down`, an arrow pointing downwards will be appended to the bottom of the section indicating that the section is growing into that direction:\n\n![Example of the different breaks](docs/assets/stack_map.svg)\n\n> The example can be executed at the root folder by running:\n>  ```bash\n>  ./linkerscope.py examples/stack_map.yaml -c examples/stack_config.yaml\n>  ```\n\n##### `break`\n\nA break or discontinuous section shortens a sized section to a fixed size by drawing a symbol representing a discontinuity across it.\nThis is specially useful when wanting to include several sections of considerable different sizes in one diagram.\nReducing the size of the biggest one helps to visually simplify the diagram and evenly distribute the space.\n\nThere are four different break styles, which can be defined by the 'break-type' style property: `~`: Wave,  `≈`: Double wave, `/`: Diagonal, `...`: Dots\n\n![Example of the different breaks](docs/assets/break_map.svg)\n\n> The example can be executed at the root folder by running:\n>  ```bash\n>  ./linkerscope.py examples/break_map.yaml -c examples/break_config.yaml\n>  ```\n\n#### Links\n\nLinks establish a connection between same addresses or sections at different areas.\n\nWhile address links are represented with a finite line between the addresses, section link drawing\ncover the whole region space. These can be used, for instance, to represent a _zoom_ in effects from one overview area\nto other area with more detail.\n\n> When drawing a section link, Linkerscope expects both start and end section addresses to be visible at both intended areas.\nIf any of those is not present, the link will not be drawn\n\nLinks are defined at root level of the configuration file under the `links` tag.\nLinks must have either `addresses` or `sections` tags or both.\n- `addresses` is a list of integers representing memory addresses\n- `sections` is a list whose elements can be either single section id's, section id's pairs (as a sublist) or both.\n  When using pairs, the first element should be the one with the lowest memory address. \nAdditionally, specific styles can be specified under the `style` tag.\n\n\n```yaml\nlinks:\n  style:\n    stroke: 'lightgray'\n    stroke-width: 1\n    fill: 'gray'\n  addresses: [ 0xe8040000, 0xe8042000 ]\n  sections: [['Bit band region', 'Bit band alias'],'ITM']\n\n```\n![Example of linked sections](docs/assets/link_map.svg)\n\n> The example can be executed at the root folder by running:\n>  ```bash\n>  ./linkerscope.py examples/link_map.yaml -c examples/link_config.yaml\n>  ```\n> \n#### Styles\n\nThe style can be defined at document level, where it will be applied to all areas, but also at area or even at section level.\nSpecifying a style at area level will override the specified properties for the whole area where it was defined.\nSpecifying it at section level, it will override style only for the specified section or group of sections. \n\n```yaml\nstyle:\n  # This is a style defined at document level\n  text-fill: 'lightgrey'\n  background: 'black'\n  stroke: '#99B898'\n  stroke-width: 1\n  font-type: 'Helvetica'\n\nareas:\n- area:\n    title: 'Internal Memory'\n    pos: [30, 50]\n    style:\n      # This is a style defined at area level, which will override fill property only applied at Main Memory area\n      fill: 'blue'\n    sections:\n    - names: [ SRAM, Flash ]\n      style:\n        # This is a style defined at section level, and will be applied only to SRAM and Flash sections\n        fill: 'green'\n        hide-address: true\n- area:\n    title: 'External Memory'\n    # ...\n```\n\nBelow a list of style properties with general use and specific for sections:\n##### General\n  - `background`, `fill`, `font-size`, `font-type`, `opacity`, `size`, `stroke`, `stroke_dasharray`, `stroke-width`, `text-stroke`,`text-fill`,`text-stroke-width`, `weight`\n\n##### Section properties:\n  - `break-type`: specify memory break type. See [`break`](#break) section\n  - `break-size`: specify memory break size in pixels. See [`break`](#break) section\n  - `growth-arrow-size`: size of the direction growth arrow. See [`Growths`](#growths) section\n  - `growth-arrow-fill`: color for the direction growth arrow. See [`Growths`](#growths) section\n  - `growth-arrow-stroke`: stroke color for the direction growth arrow. See [`Growths`](#growths) section\n  - `hide-size`: hides the size label of a section [`true | auto | false` ]\n  - `hide-name`: hides the name label of a section [`true | auto | false` ]\n  - `hide-address`: hides the address label of a section [`true | auto | false` ]\n\n#### Other properties\n\n_Document size_\n\nThe generated SVG document has a fixed size. If you want to adjust it, use the `size` property at root level to pass\ndesired document width and height in pixels.\n\n## Run some examples with LinkerScope\n\nAt the folder examples, there are a series of configurations and map `.yaml` files you can use to get a preview of what LinkerScope can do.\n\n## Roadmap\n\n- [x] Labels at specific memory addresses\n- [x] Area titles\n- [x] Links across specific areas\n- [x] Choose side of the labels\n- [x] Memory direction\n- [x] Hide specific elements\n- [ ] Memory size in bytes\n- [x] Section links across breaks\n- [x] Friendly name and identifier\n- [ ] Legend\n- [ ] Area representation different from section\n- [ ] Make `type` default to `section`\n- [x] Bug: title appears at top of each break section, if long enough\n\n## References\n\n- [YAML cheatsheet](https://quickref.me/yaml)\n- [MapViewer](https://github.com/govind-mukundan/MapViewer)\n- [LinkerMapViz](https://github.com/PromyLOPh/linkermapviz)\n\n## License\n\nDistributed under the MIT License. See `LICENSE` for more information.\n"
  },
  {
    "path": "area_view.py",
    "content": "import copy\n\nfrom helpers import safe_element_list_get, safe_element_dict_get, DefaultAppValues\nfrom labels import Labels\nfrom logger import logger\nfrom style import Style\n\n\nclass AreaView:\n    \"\"\"\n    AreaView provides the container for a given set of sections and the methods to process\n    and transform the information they contain into useful data for graphical representation\n    \"\"\"\n    pos_y: int\n    pos_x: int\n    zoom: int\n    address_to_pxl: float\n    total_height_pxl: int\n    start_address: int\n    end_address: int\n\n    def __init__(self,\n                 sections,\n                 style,\n                 area_config=[],\n                 labels=None,\n                 is_subarea = False):\n        self.sections = sections\n        self.processed_section_views = []\n        self.is_subarea = is_subarea\n        self.area = area_config\n        self.style = style\n        self.start_address = safe_element_dict_get(self.area, 'start', self.sections.lowest_memory)\n        self.end_address = safe_element_dict_get(self.area, 'end', self.sections.highest_memory)\n        self.pos_x = safe_element_list_get(\n            safe_element_dict_get(self.area, 'pos'), 0, default=DefaultAppValues.POSITION_X)\n\n        self.pos_y = safe_element_list_get(\n            safe_element_dict_get(self.area, 'pos'), 1, default=DefaultAppValues.POSITION_Y)\n\n        self.size_x = safe_element_list_get(\n            safe_element_dict_get(self.area, 'size'), 0, default=DefaultAppValues.SIZE_X)\n\n        self.size_y = safe_element_list_get(\n            safe_element_dict_get(self.area, 'size'), 1, default=DefaultAppValues.SIZE_Y)\n\n        self.labels = Labels(safe_element_dict_get(self.area, 'labels', []), style)\n        self.title = safe_element_dict_get(self.area, 'title', DefaultAppValues.TITLE)\n        self.address_to_pxl = (self.end_address - self.start_address) / self.size_y\n\n        if not self.is_subarea:\n            self._process()\n\n    def get_split_area_views(self):\n        \"\"\"\n        Get current area view split in multiple area views around break sections\n        :return: List of AreaViews\n        \"\"\"\n        return self.processed_section_views\n\n    def to_pixels(self, value) -> float:\n        \"\"\"\n        Convert a given address to pixels in an absolute manner,\n        according to the address / pixel size ratio of current area\n\n        :param value: Address to be converted to pixels\n        :return: Conversion result\n        \"\"\"\n        return value / self.address_to_pxl\n\n    def to_pixels_relative(self, value) -> float:\n        \"\"\"\n        Convert a given address to pixels in a relative manner,\n        according to the address / pixel size ratio of current area\n\n        Relative in this context means relative to the start address of the Area view. If Area View\n        starts at 0x20000 and ends at 0x30000, passing these values to this function for an area\n        with a height of 1000 pixels, will result in 0 and 1000 respectively\n\n        :param value: Address to be converted to pixels\n        :return: Conversion result\n        \"\"\"\n        return self.size_y - ((value - self.start_address) / self.address_to_pxl)\n\n    def _overwrite_sections_info(self):\n        \"\"\"\n        Override default style with section specific style\n\n        Overrides default style (normally style defined by the area it is at) and flags information\n        on a section given a new definition is provided for an specific section at the map or\n        configuration files\n        \"\"\"\n\n        for section in self.sections.get_sections():\n\n            section_style = copy.deepcopy(self.style)\n            section.style = section_style\n\n            inner_sections = safe_element_dict_get(self.area, 'sections', [])\n\n            if inner_sections is None:\n                logger.warning(\n                    \"'sections' property is declared but is empty. Field has been ignored\")\n                inner_sections = []\n\n            for element in inner_sections:\n\n                section_names = safe_element_dict_get(element, 'names', [])\n\n                if section_names is None:\n                    logger.warning(\n                        \"'sections' property is declared but is empty. Field has been ignored\")\n                    section_names = []\n\n                for item in section_names:\n                    if item == section.id:\n                        # OVERWRITE style, address, size and type if needed\n                        section_style.override_properties_from(Style(style=element.get('style')))\n                        section.address = element.get('address', section.address)\n                        section.type = element.get('type', section.type)\n                        section.size = element.get('size', section.size)\n                        # As flags can be defined previously at map file, APPEND whatever is new\n                        section.flags += element.get('flags', section.flags)\n\n    def _process(self):\n        def recalculate_subarea_size_y(start_mem_addr, end_mem_addr):\n            \"\"\"\n            Recalculates the size of the current sub-area, provided the maximum and minimum\n            memory that this area has to show\n\n            For that, it makes a relation between the memory that needs to be displayed and\n            the total non-break memory available\n\n            :param start_mem_addr: minimum memory address that the new sub-area must show\n            :param end_mem_addr: maximum memory address that the new sub-area must show\n            :return: Recalculated size for this area\n            \"\"\"\n\n            return (self.to_pixels(end_mem_addr - start_mem_addr) / total_non_breaks_size_y_px) * \\\n                   (total_non_breaks_size_y_px + expandable_size_px)\n\n        def area_config_clone(configuration, pos_y_px, size_y_px, start_mem_addr, end_mem_addr):\n            \"\"\"\n            Clones an area configuration and changes position and size\n\n            :param configuration: Area configuration to clone\n            :param pos_y_px: Position in pixels for the new cloned configuration\n            :param size_y_px: Size y in pixels of the new cloned configuration\n            :param start_mem_addr: minimum memory address that the new sub-area must show\n            :param end_mem_addr: maximum memory address that the new sub-area must show\n            :return: A new area configuration with the provided configuration and provided parameters\n            \"\"\"\n            new_configuration = copy.deepcopy(configuration)\n            new_configuration['size'] = [DefaultAppValues.SIZE_X, DefaultAppValues.SIZE_Y]\n            if new_configuration.get('pos') is None:\n                new_configuration['pos'] = [DefaultAppValues.POSITION_X, DefaultAppValues.POSITION_Y]\n            new_configuration['size'][1] = size_y_px\n            new_configuration['pos'][1] = pos_y_px - size_y_px\n            new_configuration['start'] = start_mem_addr\n            new_configuration['end'] = end_mem_addr\n            return new_configuration\n\n        self._overwrite_sections_info()\n\n        if len(self.sections.get_sections()) == 0:\n            print(\"Filtered sections produced no results\")\n            return\n\n        split_section_groups = self.sections.split_sections_around_breaks()\n\n        breaks_count = len(self.sections.filter_breaks().get_sections())\n        area_has_breaks = breaks_count >= 1\n        breaks_section_size_y_px = self.style.break_size if self.style is not None else 20\n\n        if not area_has_breaks:\n            if len(self.sections.get_sections()) == 0:\n                logger.error(f\"An area view without sections made its through the process. \"\n                             f\"This shouldn't be happening\")\n                exit(-1)\n            self.processed_section_views.append(self)\n            return\n\n        total_breaks_size_y_px = self._get_break_total_size_before_transform_px()\n        total_non_breaks_size_y_px = self._get_non_breaks_total_size_px(total_breaks_size_y_px)\n\n        # Size gained at the area after flagged sections transformed to breaks\n        expandable_size_px = total_breaks_size_y_px - (breaks_section_size_y_px * breaks_count)\n\n        last_area_pos = self.pos_y + self.size_y\n\n        for i, section_group in enumerate(split_section_groups):\n\n            # TODO: ideally, instead of doing this, we should have previously obtained an array\n            #       of sub-areas with already start and end values set. That method should live\n            #       at area class and not at sections. That is, kill the\n            #       `split_sections_around_breaks` method\n            if section_group is split_section_groups[0]:\n                start_addr = self.start_address\n                end_addr = split_section_groups[1].lowest_memory\n            elif section_group is split_section_groups[-1]:\n                end_addr = self.end_address if self.end_address > section_group.highest_memory else section_group.highest_memory\n                start_addr = split_section_groups[-2].highest_memory\n            elif section_group.is_break_section_group():\n                start_addr = section_group.lowest_memory\n                end_addr = section_group.highest_memory\n            else:\n                start_addr = split_section_groups[i - 1].highest_memory\n                end_addr = split_section_groups[i + 1].lowest_memory\n\n            # Assign new calculated size y\n            corrected_size_y_px = breaks_section_size_y_px \\\n                if section_group.is_break_section_group() \\\n                else recalculate_subarea_size_y(start_addr, end_addr)\n\n            subconfig = area_config_clone(self.area,\n                                          last_area_pos,\n                                          corrected_size_y_px,\n                                          start_addr,\n                                          end_addr)\n\n            last_area_pos = subconfig['pos'][1]\n\n            self.processed_section_views.append(AreaView(\n                sections=section_group,\n                area_config=subconfig,\n                labels=self.labels,\n                style=self.style,\n                is_subarea=True)\n            )\n\n    def _get_break_total_size_before_transform_px(self):\n        \"\"\"\n        Compute the sum of pixels that the break sections would occupy if they wouldn't be break\n        sections\n        :return: Computed size in pixels\n        \"\"\"\n        total_breaks_size_px = 0\n\n        for _break in self.sections.filter_breaks().get_sections():\n            total_breaks_size_px += self.to_pixels(_break.size)\n\n        return total_breaks_size_px\n\n    def _get_non_breaks_total_size_px(self, breaks_size_y_sum_px):\n        \"\"\"\n        Get pixel count at y of displayed memory that is not break-flagged section before\n        transformation into a break section.\n\n        That takes into account both normal sections and empty memory\n\n        :param breaks_size_y_sum_px: Total pixel count at y axis occupied by break-flagged sections before transformation into break sections.\n        :return:\n        \"\"\"\n\n        highest_mem = self.end_address if self.end_address > self.sections.highest_memory else self.sections.highest_memory\n        lowest_mem = self.start_address if self.start_address < self.sections.lowest_memory else self.sections.lowest_memory\n        return self.to_pixels(highest_mem - lowest_mem) - breaks_size_y_sum_px\n"
  },
  {
    "path": "examples/break_config.yaml",
    "content": "size: [950, 570]\n\nvariables:\n  graphite: &graphite '#2A363B'\n  pastel_green: &pastel_green '#99B898'\n\nstyle:\n  text-fill: 'black'\n  break-size: 60\n  #background: *graphite\n  fill: *pastel_green\n  hide-address: true\n  hide-size: true\n  stroke: *graphite\n  stroke-width: 0\n  font_type: 'Helvetica'\n\nareas:\n- area:\n    title: 'Wave'\n    pos: [30, 50]\n    style:\n      break-type: '~'\n    sections:\n    - names: [ External RAM ]\n      flags: break\n    - names: [ External Device, Peripheral, SRAM Area, External RAM ]\n      style:\n        stroke-width: 1\n\n- area:\n    title: 'Double-wave'\n    pos: [260, 50]\n    style:\n      break-type: '≈'\n      stroke-width: 1\n    sections:\n    - names: [ External RAM ]\n      flags: break\n\n- area:\n    title: 'Diagonal'\n    pos: [490, 50]\n    style:\n      break-type: '/'\n    sections:\n    - names: [ External RAM ]\n      flags: break\n    - names: [ External Device, Peripheral, SRAM Area, External RAM ]\n      style:\n        stroke-width: 1\n- area:\n    title: 'Dots'\n    pos: [720, 50]\n    style:\n      break-type: '...'\n      stroke-width: 1\n    sections:\n    - names: [ External RAM ]\n      flags: break\n"
  },
  {
    "path": "examples/break_map.yaml",
    "content": "map:\n- address: 0x20000000\n  size:    0x20000000\n  id: SRAM Area\n  type: area\n- address: 0x40000000\n  size:    0x20000000\n  id: Peripheral\n  type: area\n- address: 0x60000000\n  size:    0x40000000\n  id: External RAM\n  type: area\n- address: 0xA0000000\n  size:    0x40000000\n  id: External Device\n  type: area"
  },
  {
    "path": "examples/labels_config.yaml",
    "content": "size: [800, 300]\nvariables:\n  graphite: &graphite '#2A363B'\n  pastel_green: &pastel_green '#99B898'\n  pastel_yellow: &pastel_yellow '#FECEA8'\n  pastel_orange: &pastel_orange '#FF847C'\n  pastel_red: &pastel_red '#E84A5F'\n\nstyle:\n  text-fill: lightgrey\n  background: *graphite\n  hide-size: true\n\nareas:\n- area:\n    title: 'Address Link Example'\n    size: [ 200, 200 ]\n    pos: [200]\n    range: [0xE8040000, 0xE8044000]\n    style:\n      stroke: grey\n      stroke-width: 1\n      weight: 1\n      text_fill: lightgrey\n      stroke-dasharray: '1'\n\n    labels:\n      - address: 0xe8043800\n        text: In direction\n        length: 150\n        directions: in\n        style:\n          stroke-dasharray: '5,1,3,1,3'\n      - address: 0xe8042800\n        text: No direction\n        length: 150\n        side: right\n        style:\n          stroke-dasharray: '2,2'\n      - address: 0xe8041800\n        text: Out direction\n        length: 150\n        directions: out\n      - address: 0xe8041800\n        text: Label at left side\n        length: 40\n        directions: out\n        side: left\n      - address: 0xe8040800\n        text: In and Out directions\n        length: 150\n        directions: [in, out]\n\n    sections:\n    - names: [ ROM Table ]\n      style:\n        fill: *pastel_green\n    - names: [ External PPB ]\n      style:\n        fill: *pastel_yellow\n        text_fill: grey\n    - names: [ ETM, Peripheral ]\n      style:\n        fill: *pastel_orange\n    - names: [ TPIU ]\n      style:\n        fill: *pastel_red\n\n\n\n\n"
  },
  {
    "path": "examples/labels_map.yaml",
    "content": "map:\n- address: 0xE8040000\n  size:    0x00001000\n  id: TPIU\n  type: section\n- address: 0xE8041000\n  size:    0x00001000\n  id: ETM\n  type: section\n- address: 0xE8042000\n  size:    0x00001000\n  id: External PPB\n  type: section\n- address: 0xE8043000\n  size:    0x00001000\n  id: ROM Table\n  type: section"
  },
  {
    "path": "examples/link_config.yaml",
    "content": "size: [750, 650]\nvariables:\n  graphite: &graphite '#2A363B'\n  pastel_green: &pastel_green '#99B898'\n  pastel_yellow: &pastel_yellow '#FECEA8'\n  pastel_orange: &pastel_orange '#FF847C'\n  pastel_red: &pastel_red '#E84A5F'\n\nstyle:\n  text-fill: 'lightgrey'\n  background: *graphite\n  fill: '#99B898'\n  hide-size: true\n  stroke: *graphite\n  stroke-width: 1\n  text-stroke: 'black'\n  text-stroke-width: 0\n  font-type: 'Helvetica'\n  break-type: '~'\n  break-size: 100\n\nareas:\n- area:\n    title: 'Full Memory Map'\n    size: [200, 500]\n    range: [0x0, 0x100000000]\n    section-size: [0x02F00000]\n    style:\n      hide-address: true\n      fill: *pastel_green\n    sections:\n    - names: [ External Device, External Ram ]\n      fill: '#FF847C'\n      style:\n        hide-size: true\n        hide-address: true\n\n- area:\n    title: 'Address Link Example'\n    pos: [ 400, 50 ]\n    size: [ 200, 200 ]\n    range: [0xE8040000, 0xE8044000]\n    sections: # Adding the 'regions' sub-level under 'style'\n    - names: [ ROM Table ]\n      style:\n        fill: *pastel_green\n    - names: [ External PPB ]\n      style:\n        fill: *pastel_yellow\n    - names: [ ETM, Peripheral ]\n      flags: break\n      style:\n        fill: *pastel_orange\n    - names: [ TPIU ]\n      style:\n        fill: *pastel_red\n\n- area:\n    title:\n    pos: [ 400, 330 ]\n    size: [ 200, 300 ]\n    range: [0x40000000, 0x43000000]\n    sections: # Adding the 'regions' sub-level under 'style'\n    - names: [ Bit ]\n      flags: break\n      style:\n        fill: *pastel_yellow\n    - names: [ Bit band region ]\n      style:\n        fill: *pastel_orange\n    - names: [ Reserved ]\n      flags: grows-down\n\nlinks:\n  style:\n    stroke: 'lightgray'\n    stroke-width: 1\n    fill: 'gray'\n  addresses: [ 0xe8040000 ]\n  sections: [['Bit band region', 'Bit band alias'], ['TPIU', 'External PPB']]\n\n\n\n"
  },
  {
    "path": "examples/link_map.yaml",
    "content": "map:\n- address: 0x20000000\n  size:    0x20000000\n  id: SRAM Area\n  type: area\n- address: 0x40000000\n  size:    0x20000000\n  id: Peripheral\n  type: area\n- address: 0x60000000\n  size:    0x40000000\n  id: External RAM\n  type: area\n- address: 0xA0000000\n  size:    0x40000000\n  id: External Device\n  type: area\n- address: 0xE0000000\n  size:    0x08000000\n  id: Private Periph. Bus - Internal\n  type: area\n- address: 0xE8000000\n  size:    0x08000000\n  id: Private Periph. Bus - External\n  type: area\n- address: 0xF0000000\n  size:    0x08000000\n  id: Vendor Specific\n  type: area\n\n- address: 0xE8040000\n  size:    0x00001000\n  id: TPIU\n  type: section\n- address: 0xE8041000\n  size:    0x00001000\n  id: ETM\n  type: section\n- address: 0xE8042000\n  size:    0x00001000\n  id: External PPB\n  type: section\n- address: 0xE8043000\n  size:    0x00001000\n  id: ROM Table\n  type: section\n\n- address: 0xE0000000\n  size:    0x00010000\n  id: ITM\n  type: section\n- address: 0xE0010000\n  size:    0x00010000\n  id: DWT\n  type: section\n- address: 0xE0020000\n  size:    0x00010000\n  id: FPB\n  type: section\n- address: 0xE0030000\n  size:    0x000B0000\n  id: Reserved\n  type: section\n- address: 0xE00E0000\n  size:    0x00010000\n  id: NVIC\n  type: section\n- address: 0xE00F0000\n  size:    0x00010000\n  id: NVIC\n  type: section\n\n- address: 0x20000000\n  size:    0x00100000\n  id: Bit band region\n  type: section\n- address: 0x20100000\n  size:    0x01F00000\n  id: Bit\n  type: section\n- address: 0x22000000\n  size:    0x01000000\n  id: Bit band alias\n  type: section\n\n- address: 0x40000000\n  size:    0x00100000\n  id: Bit band region\n  type: section\n- address: 0x40100000\n  size:    0x01F00000\n  id: Bit\n  type: section\n- address: 0x42000000\n  size:    0x01000000\n  id: Bit band alias\n  type: section"
  },
  {
    "path": "examples/sample_config.yaml",
    "content": "size: [500,1100]\n\nvariables:\n  graphite: &graphite '#212b38'\n  cleargraphite: &cleargraphite '#37465b'\n  mydarkgreen: &mydarkgreen '#08c6ab'\n\nstyle:\n  text-fill: 'white'\n  background: *graphite\n  fill: *mydarkgreen\n  hide-address: false\n  stroke: *cleargraphite\n  stroke-width: 0.5\n  text-stroke-width: 0.1\n  font-size: 12\n  font_type: 'Helvetica'\n\nareas:\n- area:\n    title: ESP32 app space (extract)\n    range: [0x400d0000, 0x400da000]\n    section-size: [0x000010,0x0008000]\n    pos: [ 100, 50 ]\n    size: [200, 1000]\n"
  },
  {
    "path": "examples/stack_config.yaml",
    "content": "size: [300, 600]\n\nvariables:\n  green:  &green '#47b39d'\n  yellow: &yellow '#ffc153'\n  orange: &orange '#eb6b56'\n  granat: &granat '#b05f6d'\n  purple: &purple '#462446'\n  graphite: &graphite '#313b48'\n\nstyle:\n\n  hide-size: true\n  hide-address: true\n  growth-arrow-size: 2\n  stroke-width: 0\n\nareas:\n- area:\n    style:\n        background: *graphite\n    title: 'Full Memory Map'\n    sections:\n      - names: [Text]\n        style:\n          fill: *green\n      - names: [ Initialized data ]\n        style:\n          fill: *yellow\n      - names: [Uninitialized data]\n        style:\n          fill: *orange\n      - names: [ Heap ]\n        flags: grows-up\n        style:\n          fill: *granat\n      - names: [ Stack ]\n        flags: grows-down\n        style:\n          fill: *purple\n          text-fill: white"
  },
  {
    "path": "examples/stack_map.yaml",
    "content": "map:\n- address: 0x20000000\n  size:    0x10000000\n  id: Text\n  type: area\n- address: 0x30000000\n  size:    0x10000000\n  id: Initialized data\n  type: area\n- address: 0x40000000\n  size:    0x10000000\n  id: Uninitialized data\n  type: area\n- address: 0x50000000\n  size:    0x10000000\n  id: Heap\n  type: area\n  # flags: grows-up\n- address: 0xA0000000\n  size:    0x10000000\n  id: Stack\n  type: area\n  #flags: grows-down\n"
  },
  {
    "path": "examples/stm32f103_config.yaml",
    "content": "size: [1100,1100]\n\nvariables:\n  graphite: &graphite '#212b38'\n  cleargraphite: &cleargraphite '#37465b'\n  myturqoise: &myturqoise '#4aefd7'\n  mydarkgreen: &mydarkgreen '#08c6ab'\n  mypurple: &mypurple '#726eff'\n\n  #mylightblue: &mylightblue '#7bd5f5'\n  #myblue: &myblue '#1ca7ec'\n  #mydarkblue: &mydarkblue '#1f2f98'\n  mylightgrey: &mylightgrey '#F6F6F6'\n\nstyle:\n  text-fill: 'white'\n  break-size: 25\n  break-type: '≈'\n  growth-arrow-size: 2\n  growth-arrow-fill: 'white'\n  growth-arrow-stroke: 'black'\n  background: *graphite\n  fill: *mydarkgreen\n  hide-size: true\n # stroke: *mydarkblue\n  stroke-width: 0\n  text-stroke: 'black'\n  text-stroke-width: 0.1\n  font-size: 12\n  font_type: 'Helvetica'\n\nareas:\n- area:\n    title: STM32F103 Memory Space\n    range: [0x0, 0x100000000]\n    pos: [ 100, 50 ]\n    size: [200, 700]\n    style:\n      hide-size: true\n\n    sections:\n      - names: [none]\n        style:\n          hide-name: true\n          fill: *cleargraphite\n\n- area:\n    range: [0x08000000, 0x0801FFFF]\n    size: [200, 100]\n    pos: [450, 700]\n    style:\n      hide-size: false\n      fill: *myturqoise\n    sections:\n      - names: [Flash Memory]\n        style:\n          fill: *mypurple\n- area:\n    range: [0x1FFFF000, 0x1FFFF80F]\n    size: [200, 100]\n    pos: [450, 550]\n    style:\n      hide-size: false\n    sections:\n      - names: [System Memory]\n        style:\n          fill: *myturqoise\n- area:\n    range: [0xE0000000, 0xE1000000]\n    size: [200, 200]\n    pos: [450, 50]\n    style:\n      hide-size: false\n    sections:\n      - names: [ M3 Cortex Internal Peripherals ]\n\n- area:\n    title: APB memory space\n    range: [0x40000000, 0x40030000]\n    pos: [ 750, 50 ]\n    size: [ 200, 1000 ]\n\n    sections:\n      - names: [Reserved1, Reserved2, Reserved3, Reserved4, Reserved5, Reserved6, Reserved7, Reserved8, Reserved9, Reserved10, Reserved11]\n        flags: break\n        style:\n          fill: *cleargraphite\n      - names: [AFIO, EXTI, PORT A, PORT B, PORT C, PORT D, PORT E ]\n        style:\n          fill: *mypurple\n\n\nlinks:\n  style:\n    opacity: 0.2\n    fill: *mylightgrey\n    stroke: lightgrey\n  sections: [[TIM2, CRC], M3 Cortex Internal Peripherals, Flash Memory, System Memory]\n\n\n"
  },
  {
    "path": "examples/stm32f103_map.yaml",
    "content": "map:\n  - id: Aliased\n    address: 0x00000000\n    size:    0x08000000\n    type: area\n\n  - id: Flash Memory\n    address: 0x08000000\n    size:    0x0001FFFF\n    type: area\n  - id: Reserved0\n    address: 0x0801FFFF\n    size:    0x1F7FD001\n    type: area\n  - id: System Memory\n    address: 0x1FFFF000\n    size:    0x00000800\n    type: area\n  - id: Option Bytes\n    address: 0x1FFFF800\n    size:    0x0000000F\n    type: area\n  - id: Reserved00\n    address: 0x1FFFF80F\n    size:    0x00000800\n    type: area\n\n\n  - id:      none\n    address: 0x00000000\n    size:    0x20000000\n    type:    area\n  - id:      none\n    address: 0x20000000\n    size:    0x20000000\n    type:    area\n  - id:      none\n    address: 0x40000000\n    size:    0x20000000\n    type:    area\n  - id:      none\n    address: 0x60000000\n    size:    0x20000000\n    type:    area\n  - id:      none\n    address: 0x80000000\n    size:    0x20000000\n    type:    area\n  - id:      none\n    address: 0xA0000000\n    size:    0x20000000\n    type:    area\n  - id:      none\n    address: 0xC0000000\n    size:    0x20000000\n    type:    area\n\n\n\n  - id:      none\n    address: 0xE0100000\n    size:    0x0FE00000\n    type:    area\n\n  - id:      SRAM\n    address: 0x20000000\n    size:    0x08000000\n    type:    area\n\n  - id:      Peripherals\n    address: 0x40000000\n    size:    0x08000000\n    type:    area\n\n  - id:      M3 Cortex Internal Peripherals\n    address: 0xE0000000\n    size:    0x01000000\n    type:    area\n\n  - id:      TIM2\n    address: 0x40000000\n    size:    0x00000400\n    type:    area\n  - id:      TIM3\n    address: 0x40000400\n    size:    0x00000400\n    type:    area\n  - id:      TIM4\n    address: 0x40000800\n    size:    0x00000400\n    type:    area\n\n  - id: Reserved1\n    address: 0x40000C00\n    size:    0x1c00\n    type:    area\n\n  - id:      RTC\n    address: 0x40002800\n    size:    0x00000400\n    type:    area\n  - id:      WWDG\n    address: 0x40002C00\n    size:    0x00000400\n    type:    area\n  - id:      IWDG\n    address: 0x40003000\n    size:    0x00000400\n    type:    area\n\n  - id: Reserved2\n    address: 0x40003400\n    size:    0x00000400\n    type:    area\n\n  - id:      SPI2\n    address: 0x40003800\n    size:    0x00000400\n    type:    area\n\n  - id: Reserved3\n    address: 0x40003C00\n    size:    0x00000800\n    type:    area\n\n  - id:      USART2\n    address: 0x40004400\n    size:    0x00000400\n    type:    area\n  - id:      USART3\n    address: 0x40004800\n    size:    0x00000400\n    type:    area\n\n  - id: Reserved4\n    address: 0x40004C00\n    size:    0x00000800\n    type:    area\n\n  - id:      I2C1\n    address: 0x40005400\n    size:    0x00000400\n    type:    area\n  - id:      I2C2\n    address: 0x40005800\n    size:    0x00000400\n    type:    area\n  - id:      USB Registers\n    address: 0x40005C00\n    size:    0x00000400\n    type:    area\n  - id:      USB/CAN SRAM\n    address: 0x40006000\n    size:    0x00000400\n    type:    area\n  - id:      BXCAN\n    address: 0x40006400\n    size:    0x00000400\n    type:    area\n\n  - id: Reserved5\n    address: 0x40006800\n    size:    0x00000400\n    type:    area\n\n  - id:      BKP\n    address: 0x40006C00\n    size:    0x00000400\n    type:    area\n  - id:      PWR\n    address: 0x40007000\n    size:    0x00000400\n    type:    area\n\n  - id: Reserved6\n    address: 0x40007400\n    size:    0x00008C00\n    type:    area\n\n  - id:      AFIO\n    address: 0x40010000\n    size:    0x00000400\n    type:    area\n  - id:      EXTI\n    address: 0x40010400\n    size:    0x00000400\n    type:    area\n  - id:      PORT A\n    address: 0x40010800\n    size:    0x00000400\n    type:    area\n  - id:      PORT B\n    address: 0x40010C00\n    size:    0x00000400\n    type:    area\n  - id:      PORT C\n    address: 0x40011000\n    size:    0x00000400\n    type:    area\n  - id:      PORT D\n    address: 0x40011400\n    size:    0x00000400\n    type:    area\n  - id:      PORT E\n    address: 0x40011800\n    size:    0x00000400\n    type:    area\n\n  - id: Reserved7\n    address: 0x40011C00\n    size:    0x00000800\n    type:    area\n\n\n  - id:      ADC1\n    address: 0x40012400\n    size:    0x00000400\n    type:    area\n  - id:      ADC2\n    address: 0x40012800\n    size:    0x00000400\n    type:    area\n  - id:      TIM1\n    address: 0x40012C00\n    size:    0x00000400\n    type:    area\n  - id:      SPI1\n    address: 0x40013000\n    size:    0x00000400\n    type:    area\n\n  - id: Reserved7\n    address: 0x40013400\n    size:    0x00000400\n    type:    area\n\n  - id:      USART1\n    address: 0x40013800\n    size:    0x00000400\n    type:    area\n\n  - id: Reserved8\n    address: 0x40013C00\n    size:    0x0000C400\n    type:    area\n\n  - id:      DMA\n    address: 0x40020000\n    size:    0x00000400\n    type:    area\n\n  - id: Reserved9\n    address: 0x40020400\n    size:    0x00000C00\n    type:    area\n\n  - id:      RCC\n    address: 0x40021000\n    size:    0x00000400\n    type:    area\n\n  - id: Reserved10\n    address: 0x40021400\n    size:    0x00000C00\n    type:    area\n\n  - id:      Flash Interface\n    address: 0x40022000\n    size:    0x00000400\n    type:    area\n\n  - id: Reserved11\n    address: 0x40022400\n    size:    0x00000C00\n    type:    area\n\n  - id:      CRC\n    address: 0x40023000\n    size:    0x00000400\n    type:    area\n\n\n\n"
  },
  {
    "path": "gnu_linker_map_parser.py",
    "content": "import re\nimport yaml\n\nfrom section import Section\n\n\nclass GNULinkerMapParser:\n    \"\"\"\n    Parse a GNU linker map file and convert it to a yaml file for further processing\n    \"\"\"\n    def __init__(self, input_filename, output_filename):\n        self.sections = []\n        self.subsections = []\n        self.input_filename = input_filename\n        self.output_filename = output_filename\n\n    def parse(self):\n        with open(self.input_filename, 'r', encoding='utf8') as file:\n\n            file_iterator = iter(file)\n            prev_line = next(file_iterator)\n            for line in file_iterator:\n                self.process_areas(prev_line)\n                multiple_line = prev_line + line\n                self.process_sections(multiple_line)\n                prev_line = line\n\n        my_dict = {'map': []}\n        for section in self.sections:\n            my_dict['map'].append({\n                'type': 'area',\n                'address': section.address,\n                'size': section.size,\n                'id': section.id,\n                'flags': section.flags\n            })\n\n        for subsection in self.subsections:\n            my_dict['map'].append({\n                'type': 'section',\n                'parent': subsection.parent,\n                'address': subsection.address,\n                'size': subsection.size,\n                'id': subsection.id,\n                'flags': subsection.flags\n            })\n\n        with open(self.output_filename, 'w', encoding='utf8') as file:\n            yaml_string = yaml.dump(my_dict)\n            file.write(yaml_string)\n\n    def process_areas(self, line):\n        pattern = r'([.][a-z]{1,})[ ]{1,}(0x[a-fA-F0-9]{1,})[ ]{1,}(0x[a-fA-F0-9]{1,})\\n'\n\n        p = re.compile(pattern)\n        result = p.search(line)\n\n        if result is not None:\n            self.sections.append(Section(parent=None,\n                                         id=result.group(1),\n                                         address=int(result.group(2), 0),\n                                         size=int(result.group(3), 0),\n                                         _type='area'\n                                         )\n                                 )\n\n    def process_sections(self, line):\n        pattern = r'\\s(.[^.]+).([^. \\n]+)[\\n\\r]\\s+(0x[0-9a-fA-F]{16})\\s+' \\\n                  r'(0x[0-9a-fA-F]+)\\s+[^\\n]+[\\n\\r]{1}'\n\n        p = re.compile(pattern)\n        result = p.search(line)\n\n        if result is not None:\n            self.subsections.append(Section(parent=result.group(1),\n                                            id=result.group(2),\n                                            address=int(result.group(3), 0),\n                                            size=int(result.group(4), 0),\n                                            _type='section'\n                                            )\n                                    )\n"
  },
  {
    "path": "helpers.py",
    "content": "from logger import logger\n\n\nclass DefaultAppValues:\n    DOCUMENT_SIZE = (400, 700)\n    POSITION_X = 50\n    POSITION_Y = 50\n    SIZE_X = 200\n    SIZE_Y = 500\n    TITLE = ''\n\ndef safe_element_list_get(_list: [], index: int, default=None) -> int:\n    \"\"\"\n    Get an element from a list checking if both the list and the element exist\n\n    :param default: Default value to return if list or element are non existent\n    :param _list: List to extract the element from\n    :param index: Index of the element in the list\n    :return: The expected element if exists, None if it doesn't\n    \"\"\"\n\n    return _list[index] if _list is not None and len(_list) > index else default\n\n\ndef safe_element_dict_get(_dict: {}, key: str, default=None) -> int:\n    \"\"\"\n    Get an element from a dict checking if both the dict and the element exist\n\n    :param default: Default value to return if list or element are non existent\n    :param _dict: Dict to extract the element from\n    :param key: Key of the key - value pair to extract\n    :return: The expected element if exists, None if it doesn't\n    \"\"\"\n\n    return _dict[key] if _dict is not None and key in _dict else default"
  },
  {
    "path": "labels.py",
    "content": "import copy\nfrom dataclasses import dataclass\nfrom style import Style\n\n\n@dataclass\nclass Labels:\n    \"\"\"\n    Container for labels, and methods to build them from a yaml label specification\n    \"\"\"\n    sections: []\n    addresses: []\n    style: Style\n\n    def __init__(self, labels, style):\n        self.style = style\n        self.labels = self.build_labels(labels)\n\n    def build_labels(self, labels_yaml) -> []:\n        \"\"\"\n        Build a list of labels (`[Label]`) from a list of labels in a yaml format\n        :param labels_yaml: List of labels in a yaml format\n        :return: list of labels (`[Label]`)\n        \"\"\"\n        labels = []\n\n        for element in labels_yaml:\n            style = copy.deepcopy(self.style)\n            label = Label(style.override_properties_from(Style(element.get('style'))))\n\n            for key, value in element.items():\n                if key != 'style':\n                    setattr(label, key.replace('-','_'), value)\n\n            labels.append(label)\n\n        return labels\n\n\nclass Side:\n    RIGHT = 'right'\n    LEFT = 'left'\n\n\n@dataclass\nclass Label:\n    \"\"\"\n    Stores single label information for a given address.\n    Additionally, provides style information for drawing the link\n    \"\"\"\n    def __init__(self, style):\n        self.style = style\n        self.address = 0\n        self.text = 'Label'\n        self.length = 20\n        self.directions = []\n        self.side = Side.RIGHT\n"
  },
  {
    "path": "linkerscope.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport copy\n\nimport yaml\n\nfrom area_view import AreaView\nfrom helpers import safe_element_list_get, safe_element_dict_get, DefaultAppValues\nfrom links import Links\nfrom logger import logger\nfrom map_render import MapRender\nfrom style import Style\nfrom map_file_loader import MapFileLoader\nfrom sections import Sections\n\n\ndef parse_arguments():\n    parser = argparse.ArgumentParser()\n    parser.add_argument('input',\n                        help='Name of the map file,'\n                             'can be either linker .map files or .yaml descriptor')\n    parser.add_argument('--output',\n                        '-o',\n                        help='Name for the generated .svg file',\n                        default='map.svg')\n    parser.add_argument('--convert',\n                        help='Performs the conversion of a .map file to .yaml if a .map file was passed without any additional step',\n                        action='store_true',\n                        default=False,\n                        required=False\n                        )\n    parser.add_argument('--config',\n                        '-c',\n                        help='Configuration file (.yml). If not specified,'\n                             'will use config.yaml as default',\n                        )\n\n    return parser.parse_args()\n\n\ndef get_area_views(_raw_sections, _base_style, config=None):\n    \"\"\"\n    Get the area view/s with the specified style and properties (if any)\n\n    Given a list of sections, base style and configuration, this function produce a series of\n    area views with specific properties and styles. If a configuration object is passed, it will\n    be used to retrieve additional configured style and properties for one or more area views.\n    If no configuration is passed, only one area view will be generated with the default style\n    and properties\n\n    :param _raw_sections: A list of unprocessed sections to be selected from and displayed\n    :param _base_style: Base / default style to build child styles from\n    :param config: Optional, configuration object indicating number of areas, style, properties,...\n    :return: A list of configured area views\n    \"\"\"\n    def get_default_area_view(sections, style):\n        \"\"\"\n        Get an area view configured with default parameters\n        :param sections: A list of unprocessed sections to be selected from and displayed\n        :param style: Base / default style to build child styles from\n        :return: List of one element corresponding to a default area view\n        \"\"\"\n        return [AreaView(\n            sections=(Sections(sections=sections)),\n            style=copy.deepcopy(style)\n        )]\n\n    def get_custom_area_views(sections, style):\n        \"\"\"\n        Get a list of area views configured according to passed parameters\n\n        :param sections: A list of unprocessed sections to be selected from and displayed\n        :param style: Base / default style to build child styles from\n        :return: List of one or various custom area views\n        \"\"\"\n        area_views = []\n        for i, area_element in enumerate(area_configurations):\n            area_config = safe_element_dict_get(area_element, 'area')\n            section_size = safe_element_dict_get(area_config, 'section-size', None)\n            memory_range = safe_element_dict_get(area_config, 'range', None)\n            area_style = copy.deepcopy(style)\n            filtered_sections = (Sections(sections=copy.deepcopy(sections))\n                                 .filter_address_min(safe_element_list_get(memory_range, 0))\n                                 .filter_address_max(safe_element_list_get(memory_range, 1))\n                                 .filter_size_min(safe_element_list_get(section_size, 0))\n                                 .filter_size_max(safe_element_list_get(section_size, 1))\n                                 )\n            if len(filtered_sections.get_sections()) == 0:\n                logger.warning(f\"Filter for area view with index {i} doesn't result in any\"\n                               f\"section. Try re-adjusting memory range, size, ... This area \"\n                               f\"will be omitted\")\n                continue\n\n            area_views.append(\n                AreaView(\n                    sections=filtered_sections,\n                    area_config=area_config,\n                    style=area_style.override_properties_from(\n                        Style(style=safe_element_dict_get(area_config, 'style', None)))\n                )\n            )\n\n        return area_views\n\n    area_configurations = safe_element_dict_get(config, 'areas', []) or []\n\n    if len(area_configurations) == 0:\n        return get_default_area_view(_raw_sections, _base_style)\n    else:\n        return get_custom_area_views(_raw_sections, _base_style)\n\n\narguments = parse_arguments()\nraw_sections = MapFileLoader(arguments.input, arguments.convert).parse()\nbase_style = Style().get_default()\n\n\nlinks = None\ndocument_size = DefaultAppValues.DOCUMENT_SIZE\nconfiguration = {}\n\n# Apply custom configuration if configuration file is available\nif arguments.config:\n    with open(arguments.config, 'r', encoding='utf-8') as file:\n        configuration = yaml.safe_load(file)\n        if configuration is None:\n            configuration = {}\n\n    base_style_cpy = copy.deepcopy(base_style)\n    style_config = safe_element_dict_get(configuration, 'style', None)\n    base_style.override_properties_from(Style(style=style_config))\n    yaml_links = safe_element_dict_get(configuration, 'links', None)\n    links_style = base_style_cpy.override_properties_from(\n        Style(style=safe_element_dict_get(yaml_links,\n                                          'style', None)))\n\n    links = Links(yaml_links, style=links_style)\n    document_size = safe_element_dict_get(configuration, 'size', DefaultAppValues.DOCUMENT_SIZE)\n\nMapRender(area_view=get_area_views(raw_sections, base_style, configuration),\n          links=links,\n          style=base_style,\n          file=arguments.output,\n          size=document_size\n          ).draw()\n"
  },
  {
    "path": "links.py",
    "content": "import logging\n\nfrom helpers import safe_element_dict_get\nfrom style import Style\nfrom logger import CustomFormatter, logger\n\n\nclass Links:\n    \"\"\"\n    Stores the link information between given section or address\n    Additionally, provides style information for drawing the link\n    \"\"\"\n    sections: []\n    addresses: []\n    style: Style\n\n    def __init__(self, links=None, style=None):\n        self.links = links\n        self.addresses = safe_element_dict_get(self.links, 'addresses', [])\n        self.sections = safe_element_dict_get(self.links, 'sections', [])\n        self.style = style\n        self.configuration_validator()\n\n    def configuration_validator(self):\n\n        if self.addresses is None:\n            logger.warning(\"'addresses' property is declared but is empty. Field has been ignored\")\n            self.addresses = []\n\n        if self.sections is None:\n            logger.warning(\"'sections' property is declared but is empty. Field has been ignored\")\n            self.sections = []\n\n        for address in self.addresses:\n\n            if not isinstance(address, int):\n                self.addresses.remove(address)\n                logger.warning(f\"Link address '{address}' is incorrect: can only be of the type \"\n                               f\"integer. It will be ignored\")\n\n        for section_address in self.sections:\n            if not isinstance(section_address, str) and not isinstance(section_address, list):\n                self.sections.remove(section_address)\n                logger.warning(f\"Section link '{section_address}' is incorrect: can only be of the \"\n                               f\"type integer or list. It will be ignored\")\n\n            elif isinstance(section_address, list) and len(section_address) != 2:\n                self.sections.remove(section_address)\n                logger.warning(f\"Section link list '{section_address}' can only have exactly two \"\n                               f\"sections. It will be ignored\")\n\n            elif isinstance(section_address, list) and \\\n                    (not isinstance(section_address[0], str) or\n                     not isinstance(section_address[1], str)):\n\n                self.sections.remove(section_address)\n                logger.warning(f\"Section link list elements'{section_address}' must be strings. \"\n                               f\"They will be ignored\")\n"
  },
  {
    "path": "logger.py",
    "content": "import logging\n\n\n# Adaptation from https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output\nclass CustomFormatter(logging.Formatter):\n\n    grey = \"\\x1b[38;20m\"\n    yellow = \"\\x1b[33;20m\"\n    red = \"\\x1b[31;20m\"\n    bold_red = \"\\x1b[31;1m\"\n    reset = \"\\x1b[0m\"\n    format = \"%(asctime)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)\"\n\n    FORMATS = {\n        logging.DEBUG: grey + format + reset,\n        logging.INFO: grey + format + reset,\n        logging.WARNING: yellow + format + reset,\n        logging.ERROR: red + format + reset,\n        logging.CRITICAL: bold_red + format + reset\n    }\n\n    def format(self, record):\n        log_fmt = self.FORMATS.get(record.levelno)\n        formatter = logging.Formatter(log_fmt)\n        return formatter.format(record)\n\n\nlogger = logging.getLogger()\nlogger.setLevel(logging.DEBUG)\nhandler = logging.StreamHandler()\nhandler.setLevel(logging.DEBUG)\nhandler.setFormatter(CustomFormatter())\nlogger.addHandler(handler)\n"
  },
  {
    "path": "map_file_loader.py",
    "content": "import os\nimport sys\nimport yaml\nfrom logger import logger\nfrom section import Section\nfrom gnu_linker_map_parser import GNULinkerMapParser\n\n\nclass MapFileLoader:\n    \"\"\"\n    Takes input file provided by user and loads it in memory for further processing.\n    Depending on the type of file (.map or .yaml) will include an additional conversion step and\n    create a temporary .yaml file\n    \"\"\"\n    def __init__(self, file, convert):\n        self.input_filename = file\n        self.convert = convert\n\n    def parse(self):\n        _, file_extension = os.path.splitext(self.input_filename)\n\n        if file_extension == '.map':\n            self.parse_map(self.input_filename)\n            if self.convert:\n                logger.info(\".map file converted and saved as map.yaml\")\n                exit(0)\n            return self.parse_yaml('map.yaml')\n\n        if file_extension in ['.yaml', '.yml']:\n            if self.convert:\n                logger.error(\"--convert flag requires a .map file\")\n                exit(-1)\n            return self.parse_yaml(self.input_filename)\n\n        logger.error(f\"Wrong map file extension: '{file_extension}'. Use .map or .yaml files\")\n        sys.exit(-1)\n\n    @staticmethod\n    def parse_yaml(filename):\n        sections = []\n\n        with open(filename, 'r', encoding='utf-8') as file:\n            y = yaml.safe_load(file)\n\n        for element in y['map']:\n            sections.append(Section(address=element['address'],\n                                    size=element['size'],\n                                    id=element['id'],\n                                    name=element.get('name'),\n                                    parent=element.get('parent', 'none'),\n                                    _type=element.get('type', 'area'),\n                                    flags=element.get('flags', '')\n                                    )\n                            )\n\n        return sections\n\n    @staticmethod\n    def parse_map(input_filename):\n        GNULinkerMapParser(input_filename=input_filename, output_filename='map.yaml').parse()\n"
  },
  {
    "path": "map_render.py",
    "content": "from math import cos\nfrom svgwrite import Drawing\nimport svgwrite\n\nfrom helpers import DefaultAppValues\nfrom labels import Side\nfrom logger import logger\nfrom section import Section\nfrom style import Style\n\n\nclass MapRender:\n    \"\"\"\n    This class does the actual rendering of the map.\n\n    Takes all the graphical information stored at the different sections and areas, together\n    with their style and configuration, and convert them to SVG objects (see `draw()` function)\n    \"\"\"\n    dwg: Drawing\n    pointer_y: int\n\n    def __init__(self, area_view, links, file='map.svg', size=DefaultAppValues.DOCUMENT_SIZE, **kwargs):\n        self.style = kwargs.get('style')\n        self.type = type\n        self.area_views = area_view\n        self.current_style = Style()\n        self.links = links\n        self.links_sections = self._get_valid_linked_sections(links.sections) if links is not None else []\n        self.file = file\n        self.size = size\n        self.dwg = svgwrite.Drawing(file,\n                                    profile='full',\n                                    size=self.size\n                                    )\n\n    def _get_valid_linked_sections(self, linked_sections):\n        \"\"\"\n        Get a valid list of linked sections to draw, given a list of wished sections to be linked\n\n        For a link to be valid, the starting and ending addresses of the linked section/s must be\n        visible and available inside of at least one single area\n\n        :param linked_sections: List of sections or pair of sections to be linked\n        :return: List of valid (start, end) addresses for sections\n        \"\"\"\n\n        l_sections = []\n\n        # Iterate through all linked sections\n        for linked_section in linked_sections:\n            appended = False\n            multi_section = False\n\n            # Check if we are dealing with a link for a single section or for many of them.\n            # That is, user passed a string or a list of two strings\n            if isinstance(linked_section, list):\n                multi_section = True\n\n            # Iterate through all available areas checking if this is a valid link: i.e, the\n            # starting and ending addresses of the linked section/s is visible and available\n            # inside of a single area\n            for area in self.area_views:\n                start = None\n                end = None\n\n                # Exit loop if we found that the link is valid\n                if appended:\n                    break\n\n                for section in area.sections.get_sections():\n                    # If single section, the start and end address of the linked section equals\n                    # those of the section\n                    if not multi_section:\n                        if section.id == linked_section:\n                            l_sections.append([section.address, section.address + section.size])\n                            appended = True\n                            break\n                    # If multiple section, the start and end address of the linked section are the\n                    # start of the first provided section and the end of the second provided section\n                    # respectively\n                    else:\n                        if section.id == linked_section[0]:\n                            start = section.address\n                        elif section.id == linked_section[1]:\n                            end = section.address + section.size\n\n                        # If before finishing the iteration on this area, we found a valid start and\n                        # end address, we can append this linked section to the list\n                        if start is not None and end is not None:\n                            l_sections.append([start, end])\n                            appended = True\n                            break\n\n                # If we finish iterating the area, and we have a valid start (or end) address but\n                # the section was not appended, means that the other end of the section is at\n                # another area, and that is not valid\n                if multi_section and not appended and (start is not None or end is not None):\n                    logger.warning(\"A multisection zoom region was specified for two sections\"\n                                   f\"of different areas, which is not supported: \"\n                                   f\"{linked_section[0]}, {linked_section[1]}\")\n                    break\n\n        return l_sections\n\n    def draw(self):\n\n        dwg = self.dwg\n\n        def _draw_area(area) -> svgwrite.container.Group:\n            \"\"\"\n            Draw given area\n\n            Draw the title for the area, then, for each subarea proceed to draw\n            the different elements. Those are the frame and sections, with its\n            information such as labels, name, memory, etc...\n\n            :param area: Area to be drawn\n            :return Container with an area to be drawn\n            \"\"\"\n            area_group = dwg.g()\n            title = self._make_title(area)\n            title.translate(area.pos_x, area.pos_y)\n            area_group.add(title)\n\n            for sub_area in area.get_split_area_views():\n                subarea_group = dwg.g()\n\n                subarea_group.add(self._make_main_frame(sub_area))\n\n                for section in sub_area.sections.get_sections():\n                    if section.is_hidden():\n                        continue\n                    self._make_section(subarea_group, section, sub_area)\n\n                subarea_group.translate(sub_area.pos_x, sub_area.pos_y)\n\n                area_group.add(subarea_group)\n\n            return area_group\n\n        def draw_section_links() -> svgwrite.container.Group:\n            linked_sections_group = dwg.g()\n            for section_link in self.links_sections:\n                is_drawn = False\n                for _area_view in self.area_views[1:]:\n                    if section_link[0] >= _area_view.sections.lowest_memory and \\\n                            section_link[1] <= _area_view.sections.highest_memory and \\\n                            section_link[0] >= self.area_views[0].sections.lowest_memory and \\\n                            section_link[1] <= self.area_views[0].sections.highest_memory:\n                        linked_sections_group.add(self._make_poly(_area_view,\n                                                                  section_link[0],\n                                                                  section_link[1],\n                                                                  self.links.style))\n                        is_drawn = True\n                        break\n                if not is_drawn:\n                    logger.warning(f\"Starting or ending point of the zoom region is outside the \"\n                                   f\"shown areas for the link with addresses \"\n                                   f\"[{hex(section_link[0])}, {hex(section_link[1])}]\")\n\n            return linked_sections_group\n\n        def draw_labels() -> svgwrite.container.Group:\n            global_labels = dwg.g()\n            for area in self.area_views:\n                for subarea in area.get_split_area_views():\n                    g = dwg.g()\n\n                    if subarea.labels is not None:\n                        for label in subarea.labels.labels:\n\n                            if subarea.sections.has_address(label.address):\n                                g.add(self._make_label(label, subarea))\n\n                    g.translate(subarea.pos_x, subarea.pos_y)\n                    global_labels.add(g)\n            return global_labels\n\n        def draw_growths() -> svgwrite.container.Group:\n            # We need to do another pass once all areas are drawn in order to be able to properly\n            # draw the growth arrows without the break areas hiding them. Also, as we do stuff\n            # outside the loop where the areas are drawn, we loose the reference for translation,\n            # and we have to manually translate the grows here\n            for _area_view in self.area_views:\n                for subarea in _area_view.get_split_area_views():\n\n                    area_growth = dwg.g()\n                    for section in subarea.sections.get_sections():\n                        if section.is_hidden():\n                            continue\n                        area_growth.add(self._make_growth(section))\n\n                    area_growth.translate(subarea.pos_x, subarea.pos_y)\n                    growths_group.add(area_growth)\n            return growths_group\n\n        def draw_links() -> svgwrite.container.Group:\n            lines_group = dwg.g()\n            for address in self.links.addresses:\n                lines_group.add(self._make_link(address, self.links.style))\n            return lines_group\n\n        dwg.add(dwg.rect(insert=(0, 0),\n                         size=('100%', '100%'),\n                         rx=None,\n                         ry=None,\n                         fill=self.style.background))\n\n        growths_group = dwg.g()\n\n        dwg.add(draw_section_links()) if self.links_sections is not None else None\n        dwg.add(draw_links()) if self.links is not None else None\n\n        for area_view in self.area_views:\n            dwg.add(_draw_area(area_view))\n\n        dwg.add(draw_labels())\n        dwg.add(draw_growths())\n        dwg.save()\n\n    def _make_title(self, area_view):\n        title_pos_x = area_view.size_x / 2\n        title_pos_y = -20\n        return self._make_text(area_view.title,\n                               (title_pos_x, title_pos_y),\n                               style=area_view.style,\n                               anchor='middle',\n                               text_type='title'\n                               )\n\n    def _make_growth(self, section: Section) -> svgwrite.container.Group:\n        \"\"\"\n        Make the growth arrows for the sections that have it\n        :param section: Section for which to draw the arrow\n        :return: A SVG group containing the new arrows\n        \"\"\"\n        group = self.dwg.g()\n        # Why grows doesn't draw on a break section?\n        multiplier = section.style.growth_arrow_size\n        mid_point_x = (section.pos_x + section.size_x) / 2\n        arrow_head_width = 5 * multiplier\n        arrow_head_height = 10 * multiplier\n        arrow_length = 10 * multiplier\n        arrow_tail_width = 1 * multiplier\n\n        def _make_growth_arrow_generic(arrow_start_y, direction):\n            points_list = [(mid_point_x - arrow_tail_width, arrow_start_y),\n                           (mid_point_x - arrow_tail_width,\n                            arrow_start_y - direction * arrow_length),\n                           (mid_point_x - arrow_head_width,\n                            arrow_start_y - direction * arrow_head_height),\n                           (mid_point_x,\n                            arrow_start_y - direction * (arrow_length + arrow_head_height)),\n                           (mid_point_x + arrow_head_width,\n                            arrow_start_y - direction * arrow_head_height),\n                           (mid_point_x + arrow_tail_width,\n                            arrow_start_y - direction * arrow_length),\n                           (mid_point_x + arrow_tail_width,\n                            arrow_start_y)]\n\n            group.add(self.dwg.polyline(points_list,\n                                        stroke=section.style.growth_arrow_stroke,\n                                        stroke_width=1,\n                                        fill=section.style.growth_arrow_fill))\n\n        if section.is_grow_up():\n            _make_growth_arrow_generic(section.pos_y, 1)\n        if section.is_grow_down():\n            _make_growth_arrow_generic(section.pos_y + section.size_y, -1)\n\n        return group\n\n    def _make_main_frame(self, area_view):\n        return self.dwg.rect((0, 0), (area_view.size_x, area_view.size_y),\n                             fill=area_view.style.background,\n                             stroke=area_view.style.stroke,\n                             stroke_width=area_view.style.stroke_width)\n\n    def _make_box(self, section: Section):\n        return self.dwg.rect((section.pos_x, section.pos_y),\n                             (section.size_x, section.size_y),\n                             fill=section.style.fill,\n                             stroke=section.style.stroke,\n                             stroke_width=section.style.stroke_width)\n\n    def _make_break(self, section: Section) -> svgwrite.container.Group:\n        \"\"\"\n        Make a break representation for a given section.\n\n        Depending on the selected break type (at style/break_type), break can be wave (~), double\n        wave(≈), diagonal(/) or dots(...)\n        :param section: Section for which the break wants to be created\n        :return: SVG group container with the breaks graphics\n        \"\"\"\n        group = self.dwg.g()\n        mid_point_x = (section.pos_x + section.size_x) / 2\n        mid_point_y = (section.pos_y + section.size_y) / 2\n        style = section.style\n\n        def _make_break_dots(_section: Section) -> svgwrite.container.Group:\n            \"\"\"\n            Make a break representation using dot style\n\n            :param _section: Section for which the break wants to be created\n            :return: SVG group container with the breaks graphics\n            \"\"\"\n            rectangle = self.dwg.rect((_section.pos_x, _section.pos_y),\n                                      (_section.size_x, _section.size_y))\n\n            rectangle.fill(style.fill)\n            rectangle.stroke(style.stroke, width=style.stroke_width)\n\n            group.add(rectangle)\n\n            points_list = [\n                (mid_point_x, mid_point_y),\n                (mid_point_x, mid_point_y + 12),\n                (mid_point_x, mid_point_y - 12),\n            ]\n\n            for points_set in points_list:\n                group.add(self.dwg.circle(points_set, 3, fill=style.text_fill))\n\n            return group\n\n        def _make_break_wave(_section: Section) -> svgwrite.container.Group:\n            \"\"\"\n            Make a break representation using wave style\n\n            :param _section: Section for which the break wants to be created\n            :return: SVG group container with the breaks graphics\n            \"\"\"\n            wave_len = _section.size_x + 1\n            shifts = [(-5, 2/5, 0), (5, 3 / 5, _section.size_y), ]\n\n            for shift in shifts:\n                points = [(i, mid_point_y + shift[0] + 2 * cos(i / 24)) for i in range(wave_len)]\n                points.extend(\n                    [\n                        (_section.pos_x + _section.size_x,\n                         (_section.pos_y + _section.size_y) * shift[1]),\n                        (_section.pos_x + _section.size_x, _section.pos_y + shift[2]),\n                        (_section.pos_x, _section.pos_y + shift[2]),\n                        (_section.pos_x, mid_point_y + shift[0] + 2 * cos(_section.pos_x / 24)),\n                    ]\n                )\n\n                group.add(self.dwg.polyline(points,\n                                            stroke=style.stroke,\n                                            stroke_width=style.stroke_width,\n                                            fill=style.fill))\n\n            return group\n\n        def _make_break_double_wave(_section: Section) -> svgwrite.container.Group:\n            \"\"\"\n            Make a break representation using double wave style\n\n            :param _section: Section for which the break wants to be created\n            :return: SVG group container with the breaks graphics\n            \"\"\"\n            points_list = [[\n                (_section.pos_x, (_section.pos_y + _section.size_y) * 2 / 5),\n                (_section.pos_x, _section.pos_y),\n                (_section.pos_x + _section.size_x, _section.pos_y),\n                (_section.pos_x + _section.size_x, (_section.pos_y + _section.size_y) * 2 / 5),\n            ],\n                [\n                    (_section.pos_x, (_section.pos_y + _section.size_y) * 3 / 5),\n                    (_section.pos_x, _section.pos_y + _section.size_y),\n                    (_section.pos_x + _section.size_x, _section.pos_y + _section.size_y),\n                    (_section.pos_x + _section.size_x, (_section.pos_y + _section.size_y) * 3 / 5),\n                ]\n            ]\n\n            rectangle = self.dwg.rect((_section.pos_x, _section.pos_y),\n                                      (_section.size_x, _section.size_y))\n            rectangle.fill(section.style.fill)\n            group.add(rectangle)\n\n            for points_set in points_list:\n                group.add(self.dwg.polyline(points_set,\n                                            stroke=style.stroke,\n                                            stroke_width=style.stroke_width,\n                                            fill='none'))\n            wave_length = 20\n            shifts = [(0, -5),\n                      (0, +5),\n                      (_section.size_x, -5),\n                      (_section.size_x, +5),\n                      ]\n\n            for shift in shifts:\n                points = [(i - wave_length / 2 + shift[0], mid_point_y + shift[1] + cos(i / 2))\n                          for i in range(wave_length)]\n\n                group.add(self.dwg.polyline(points,\n                                            stroke=style.stroke,\n                                            stroke_width=style.stroke_width,\n                                            fill='none'))\n\n            return group\n\n        def _make_break_diagonal(_section: Section) -> svgwrite.container.Group:\n            \"\"\"\n            Make a break representation using diagonal style\n\n            :param _section: Section for which the break wants to be created\n            :return: SVG group container with the breaks graphics\n            \"\"\"\n            points_list = [[(_section.pos_x, _section.pos_y),\n                            (_section.pos_x + _section.size_x, _section.pos_y),\n                            (_section.pos_x + _section.size_x,\n                             (_section.pos_y + _section.size_y) * 3 / 10),\n                            (_section.pos_x, (_section.pos_y + _section.size_y) * 5 / 10),\n                            (_section.pos_x, _section.pos_y)\n                            ], [(_section.pos_x, _section.pos_y + _section.size_y),\n                                (_section.pos_x + _section.size_x,\n                                 _section.pos_y + _section.size_y),\n                                (_section.pos_x + _section.size_x,\n                                 (_section.pos_y + _section.size_y) * 5 / 10),\n                                (_section.pos_x, (_section.pos_y + _section.size_y) * 7 / 10),\n                                (_section.pos_x, _section.pos_y + _section.size_y),\n                                ]]\n\n            for points_set in points_list:\n                group.add(self.dwg.polyline(points_set,\n                                            stroke=style.stroke,\n                                            stroke_width=style.stroke_width,\n                                            fill=style.fill))\n\n            return group\n\n        breaks = [('/', _make_break_diagonal),\n                  ('≈', _make_break_double_wave),\n                  ('~', _make_break_wave),\n                  ('...', _make_break_dots), ]\n\n        for _break in breaks:\n            if style.break_type == _break[0]:\n                return _break[1](section)\n\n    def _make_text(self,\n                   text,\n                   position,\n                   style,\n                   text_type='normal',\n                   **kwargs):\n\n        if text_type == 'title':\n            size = '24px'\n        elif text_type == 'small':\n            size = '12px'\n        else:\n            size = style.font_size\n\n        return self.dwg.text(text, insert=(position[0], position[1]),\n                             stroke=style.text_stroke,\n                             # focusable='true',\n                             fill=style.text_fill,\n                             stroke_width=style.text_stroke_width,\n                             font_size=size,\n                             font_weight=\"normal\",\n                             font_family=style.font_type,\n                             text_anchor=kwargs.get('anchor', 'middle'),\n                             alignment_baseline=kwargs.get('baseline', 'middle')\n                             )\n\n    def _make_name(self, section):\n        name = section.name if section.name is not None else section.id\n        return self._make_text(name,\n                               (section.name_label_pos_x, section.name_label_pos_y),\n                               style=section.style,\n                               anchor='middle',\n                               )\n\n    def _make_size_label(self, section):\n        return self._make_text(hex(section.size),\n                               (section.size_label_pos[0], section.size_label_pos[1]),\n                               section.style,\n                               anchor='start',\n                               baseline='hanging',\n                               text_type='small'\n                               )\n\n    def _make_address(self, section):\n        return self._make_text(hex(section.address),\n                               (section.addr_label_pos_x, section.addr_label_pos_y),\n                               anchor='start',\n                               style=section.style)\n\n    def _make_section(self, group, section: Section, area_view):\n        section.size_x = area_view.size_x\n        section.size_y = area_view.to_pixels(section.size)\n        section.pos_y = area_view.to_pixels(area_view.end_address - section.size - section.address)\n        section.pos_x = 0\n\n        if section.is_break():\n            group.add(self._make_break(section))\n        else:\n            group.add(self._make_box(section))\n            if not section.is_name_hidden():\n                group.add(self._make_name(section))\n            if not section.is_address_hidden():\n                group.add(self._make_address(section))\n            if not section.is_size_hidden():\n                group.add(self._make_size_label(section))\n\n        return group\n\n    def _get_points_for_address(self, address, area_view):\n        left_block_view = self.area_views[0]\n        right_block_view = area_view\n\n        left_block_x = left_block_view.size_x + left_block_view.pos_x\n        left_block_x2 = left_block_x + 30\n        left_block_y = left_block_view.pos_y + left_block_view.to_pixels_relative(address)\n\n        right_block_x = area_view.pos_x\n        right_block_x2 = right_block_x - 30\n        right_block_y = right_block_view.pos_y + right_block_view.to_pixels_relative(address)\n\n        return [(left_block_x, left_block_y),\n                (left_block_x2, left_block_y),\n                (right_block_x2, right_block_y),\n                (right_block_x, right_block_y),\n                ]\n\n    def _make_poly(self, area_view, start_address, end_address, style):\n\n        def find_right_subarea_view(address, area):\n            \"\"\"\n            Given an area, find the subarea where the provided address is\n\n            :param address: Address to look for\n            :param area: Area that contains the subarea to be found\n            :return: Found subarea, if not found, parent area\n            \"\"\"\n            for subarea in area.get_split_area_views():\n                if subarea.start_address <= address <= subarea.end_address:\n                    return subarea\n            return area\n\n        points = []\n\n        end_subarea = find_right_subarea_view(end_address, area_view)\n        start_subarea = find_right_subarea_view(start_address, area_view)\n\n        _reversed = self._get_points_for_address(end_address, end_subarea)\n        _reversed.reverse()\n        points.extend(self._get_points_for_address(start_address, start_subarea))\n        points.extend(_reversed)\n\n        return self.dwg.polyline(points,\n                                 stroke=style.stroke,\n                                 stroke_width=style.stroke_width,\n                                 fill=style.fill,\n                                 opacity=style.opacity)\n\n    def _make_arrow_head(self, label, direction='down'):\n        if direction == 'left':\n            angle = 90\n        elif direction == 'right':\n            angle = 270\n        elif direction == 'up':\n            angle = 0\n        else:\n            angle = 180\n\n        arrow_head_width = 5 * label.style.weight\n        arrow_head_height = 10 * label.style.weight\n        group = self.dwg.g()\n        points_list = [(0, 0 - arrow_head_height),\n                       (0 - arrow_head_width, 0 - arrow_head_height),\n                       (0, 0),\n                       (0 + arrow_head_width, 0 - arrow_head_height),\n                       (0, 0 - arrow_head_height),\n                       ]\n\n        poly = self.dwg.polyline(points_list,\n                                 stroke=label.style.stroke,\n                                 stroke_width=1,\n                                 fill=label.style.stroke)\n        poly.rotate(angle, center=(0, 0))\n        group.add(poly)\n\n        return group\n\n    def _make_label(self, label, area_view):\n        line_label_spacer = 3\n        g = self.dwg.g()\n        address = label.address\n        text = label.text\n        label_length = label.length\n\n        if address is None:\n            raise KeyError(\"A label without address was found\")\n\n        if label.side == Side.RIGHT:\n            pos_x_d = area_view.size_x\n            direction = 1\n            anchor = 'start'\n\n        else:\n            pos_x_d = 0\n            direction = -1\n            anchor = 'end'\n\n        pos_y = area_view.to_pixels_relative(address)\n        points = [(0 + pos_x_d, pos_y), (direction*(label_length + pos_x_d), pos_y)]\n\n        def add_arrow_head(_direction):\n            arrow_direction = 'right'\n            if 'in' == _direction:\n                if label.side == Side.LEFT:\n                    arrow_direction = 'right'\n                elif label.side == Side.RIGHT:\n                    arrow_direction = 'left'\n\n                arrow_head_x = pos_x_d\n\n            elif 'out' == _direction:\n                if label.side == Side.LEFT:\n                    arrow_direction = 'left'\n                elif label.side == Side.RIGHT:\n                    arrow_direction = 'right'\n\n                arrow_head_x = direction * (label_length + pos_x_d)\n\n            else:\n                logger.warning(f\"Invalid direction {_direction} provided\")\n                return\n\n            g.add(self._make_arrow_head(label, direction=arrow_direction))\\\n                .translate(arrow_head_x, pos_y)\n\n        if type(label.directions) == str:\n            add_arrow_head(label.directions)\n        elif type(label.directions) == list:\n            for head_direction in label.directions:\n                add_arrow_head(head_direction)\n\n        g.add(self._make_text(text,\n                              (direction*(pos_x_d + label_length + line_label_spacer), pos_y),\n                              label.style,\n                              anchor=anchor))\n\n        g.add(self.dwg.polyline(points,\n                                stroke=label.style.stroke,\n                                stroke_dasharray=label.style.stroke_dasharray,\n                                stroke_width=label.style.stroke_width\n                                ))\n        return g\n\n    def _make_link(self, address, style):\n        hlines = self.dwg.g(id='hlines', stroke='grey')\n\n        for area_view in self.area_views[1:]:\n            for subarea in area_view.get_split_area_views():\n\n                if not subarea.sections.has_address(address):\n                    continue\n\n                def _make_line(x1, y1, x2, y2):\n                    return self.dwg.line(start=(x1, y1), end=(x2, y2),\n                                         stroke_width=style.stroke_width,\n                                         stroke=style.stroke)\n\n                points = self._get_points_for_address(address, subarea)\n\n                hlines.add(_make_line(x1=points[0][0], y1=points[0][1],\n                                      x2=points[1][0], y2=points[1][1]))\n\n                hlines.add(_make_line(x1=points[1][0], y1=points[1][1],\n                                      x2=points[2][0], y2=points[2][1]))\n\n                hlines.add(_make_line(x1=points[2][0], y1=points[2][1],\n                                      x2=points[3][0], y2=points[3][1]))\n        return hlines\n"
  },
  {
    "path": "requirements.txt",
    "content": "svgwrite==1.4.3\npyyaml==6.0.1\n"
  },
  {
    "path": "section.py",
    "content": "from style import Style\n\n\nclass Section:\n    \"\"\"\n    Holds logical and graphical information for a given section, as well as other properties such as\n    style, visibility, type, etc...\n    \"\"\"\n    size: int\n    address: int\n    id: str\n    size_x: int\n    size_y: int\n    pos_x: int\n    pos_y: int\n    label_offset: int = 10\n    style: Style\n\n    def __init__(self, size, address, id, _type, parent, flags=[], name=None):\n        self.type = _type\n        self.parent = parent\n        self.size = size\n        self.address = address\n        self.id = id\n        self.name = name\n        self.size_y = 0\n        self.size_x = 0\n        self.style = Style()\n        self.flags = flags\n\n    def is_grow_up(self):\n        return 'grows-up' in self.flags\n\n    def is_grow_down(self):\n        return 'grows-down' in self.flags\n\n    def is_break(self):\n        return 'break' in self.flags\n\n    def is_hidden(self):\n        return 'hidden' in self.flags\n\n    def _should_element_be_hidden(self, attribute):\n        return True if str(attribute) in ['True', 'yes'] \\\n            else False if str(attribute) in ['False', 'no'] \\\n            else self.size_y < 20\n\n    def is_address_hidden(self):\n        return self._should_element_be_hidden(self.style.hide_address)\n\n    def is_name_hidden(self):\n        return self._should_element_be_hidden(self.style.hide_name)\n\n    def is_size_hidden(self):\n        return self._should_element_be_hidden(self.style.hide_size)\n\n    @property\n    def addr_label_pos_x(self):\n        return self.size_x + self.label_offset\n\n    @property\n    def addr_label_pos_y(self):\n        return self.pos_y + self.size_y\n\n    @property\n    def name_label_pos_x(self):\n        return self.size_x / 2\n\n    @property\n    def size_label_pos(self):\n        return self.pos_x + 2, self.pos_y + 2\n\n    @property\n    def name_label_pos_y(self):\n        return self.pos_y + (self.size_y / 2)\n"
  },
  {
    "path": "sections.py",
    "content": "from section import Section\n\n\nclass Sections:\n    \"\"\"\n    Provide methods and to select and filter sections according to their base address, size, parent,\n    type,...\n    \"\"\"\n    sections: [Section] = []\n\n    def __init__(self, sections: [Section]):\n        self.sections = sections\n\n    def get_sections(self) -> [Section]:\n        return self.sections\n\n    @property\n    def highest_section(self) -> int:\n        return max(self.sections, key=lambda x: x.address)\n\n    @property\n    def highest_address(self) -> int:\n        return max(self.sections, key=lambda x: x.address).address\n\n    @property\n    def highest_memory(self) -> int:\n        section = max(self.sections, key=lambda x: x.address + x.size)\n        return section.address + section.size\n\n    @property\n    def lowest_memory(self) -> int:\n        return min(self.sections, key=lambda x: x.address).address\n\n    @property\n    def lowest_size(self) -> int:\n        return min(self.sections, key=lambda x: x.size).size\n\n    def has_address(self, address: int) -> bool:\n        for section in self.sections:\n            if section.address <= address <= (section.address + section.size):\n                return True\n        return False\n\n    def is_break_section_group(self):\n        for section in self.get_sections():\n            if section.is_break():\n                return True\n        return False\n\n    def filter_size_min(self, size_bytes: int):\n        return Sections(self.sections) if size_bytes is None \\\n            else Sections(list(filter(lambda item: item.size > size_bytes, self.sections)))\n\n    def filter_size_max(self, size_bytes: int):\n        return Sections(self.sections) if size_bytes is None \\\n            else Sections(list(filter(lambda item: item.size < size_bytes, self.sections)))\n\n    def filter_address_max(self, address_bytes: int):\n        return Sections(self.sections) if address_bytes is None \\\n            else Sections(list(filter(lambda item: (item.address + item.size)\n                                                   <= address_bytes, self.sections)))\n\n    def filter_address_min(self, address_bytes: int):\n        return Sections(self.sections) if address_bytes is None \\\n            else Sections(list(filter(lambda item: item.address >= address_bytes, self.sections)))\n\n    def filter_type(self, _type: str):\n        return Sections(self.sections) if _type is None \\\n            else Sections(list(filter(lambda item: item.filter_type == _type, self.sections)))\n\n    def filter_parent(self, parent: str):\n        return Sections(self.sections) if parent is None \\\n            else Sections(list(filter(lambda item: item.filter_parent == parent, self.sections)))\n\n    def filter_breaks(self):\n        return Sections(list(filter(lambda item: item.is_break(), self.sections)))\n\n    def split_sections_around_breaks(self) -> []:\n        \"\"\"\n        Split a Sections object into different Sections objects having a break section as delimiter\n\n        :return: A list of Section objects\n        \"\"\"\n        split_sections = []\n        previous_break_end_address = self.lowest_memory\n\n        breaks = self.filter_breaks().get_sections()\n\n        for _break in breaks:\n\n            # Section that covers from previous break till start of this break\n            # If it was the first break, will cover from begining of the whole area to this break.\n            # Only append if search returns more than 0 counts\n            s = Sections(sections=self.sections) \\\n                .filter_address_max(_break.address) \\\n                .filter_address_min(previous_break_end_address)\n            if len(s.get_sections()) > 0:\n                split_sections.append(s)\n\n            # This section covers the break itself\n            split_sections.append(Sections(sections=[_break]))\n            previous_break_end_address = _break.address + _break.size\n\n        # Section that covers from the last break end address to the end of the whole area. Only\n        # append if search returns more than 0 counts\n        last_group = Sections(sections=self.sections) \\\n            .filter_address_max(self.highest_memory) \\\n            .filter_address_min(previous_break_end_address)\n\n        if len(last_group.sections) > 0:\n            split_sections.append(last_group)\n\n        return split_sections\n"
  },
  {
    "path": "style.py",
    "content": "class Style:\n    \"\"\"\n    Holds style for different rendering objects\n    \"\"\"\n    # Non SVG\n    background: str\n    break_type: str\n    break_size: int\n    growth_arrow_size: float\n    growth_arrow_stroke: str\n    growth_arrow_fill: str\n    stroke_dasharray: str\n    hide_size: str\n    hide_name: str\n    hide_address: str\n\n    # SVG\n    fill: str\n    stroke: str\n    stroke_width: int\n    size: int\n    font_size: int\n    font_type: str\n    weight: int\n    opacity: int\n\n    text_stroke: str\n    text_stroke_width: int\n    text_fill: str\n\n    weight: int\n\n    def __init__(self, style=None):\n        if style is not None:\n            for key, value in style.items():\n                setattr(self, key.replace('-', '_'), style.get(key, value))\n\n    def override_properties_from(self, style):\n        \"\"\"\n        Modify self by adding additional members available at the provided style\n\n        :param style: Style whose members wants to be added\n        :return: New merged styl\n        \"\"\"\n        members = [attr for attr in dir(style) if\n                   not callable(getattr(style, attr)) and not attr.startswith(\"__\") and getattr(\n                       style, attr) is not None]\n\n        for member in members:\n            value = getattr(style, member)\n            setattr(self, member, value)\n\n        return self\n\n    @staticmethod\n    def get_default():\n        \"\"\"\n        Get an initialized default Style instance\n        :return: A default initialized Style instance\n        \"\"\"\n\n        default_style = Style()\n        default_style.break_type = '≈'\n        default_style.break_size = 20\n\n        default_style.growth_arrow_size = 1\n\n        default_style.background = 'white'\n        default_style.stroke = 'black'\n        default_style.stroke_width = 1\n        default_style.size = 2\n\n        default_style.font_size = 16\n        default_style.font_type = 'Helvetica'\n\n        default_style.weight = 1\n        default_style.opacity = 1\n\n        default_style.text_stroke = 'black'\n        default_style.text_fill = 'black'\n        default_style.text_stroke_width = 0\n\n        default_style.fill = 'lightgrey'\n        default_style.growth_arrow_fill = 'white'\n        default_style.growth_arrow_stroke = 'black'\n        default_style.stroke_dasharray = '3,2'\n        default_style.weight = 2\n        default_style.hide_size = 'auto'\n        default_style.hide_name = 'auto'\n        default_style.hide_address = 'auto'\n\n        return default_style\n"
  }
]