Full Code of raulgotor/linkerscope for AI

main ba13813088f8 cached
30 files
104.6 KB
25.9k tokens
91 symbols
1 requests
Download .txt
Repository: raulgotor/linkerscope
Branch: main
Commit: ba13813088f8
Files: 30
Total size: 104.6 KB

Directory structure:
gitextract_tz97aatf/

├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── main.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── area_view.py
├── examples/
│   ├── break_config.yaml
│   ├── break_map.yaml
│   ├── labels_config.yaml
│   ├── labels_map.yaml
│   ├── link_config.yaml
│   ├── link_map.yaml
│   ├── sample_config.yaml
│   ├── stack_config.yaml
│   ├── stack_map.yaml
│   ├── stm32f103_config.yaml
│   └── stm32f103_map.yaml
├── gnu_linker_map_parser.py
├── helpers.py
├── labels.py
├── linkerscope.py
├── links.py
├── logger.py
├── map_file_loader.py
├── map_render.py
├── requirements.txt
├── section.py
├── sections.py
└── style.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: raulgotor
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']


================================================
FILE: .github/workflows/main.yml
================================================
name: Pylint

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.8", "3.9", "3.10"]
    steps:
    - uses: actions/checkout@v3
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v3
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install pylint
    - name: Analysing the code with pylint
      run: |
        pylint $(git ls-files '*.py') --disable=C0114,R0902,C0116 --fail-under=9


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# IDE
.idea

# OS
.DS_Store

# Artifacts
*.svg

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/#use-with-ide
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/


================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
* `auto` property to `hide-*` attributes so LinkerScope can explicitly decide whether the attribute should be hidden due to overlapping issues 
* `hidden` property to sections to allow hiding one section while still computing its properties 

## [0.3.1] - 2024-02-03

### Added
* `--convert` flag to simply convert `.map` files to `.yaml` files

## [0.3.0] - 2024-02-03

### Added
* Title feature and property (`title`) for the different areas
* `hide-name`, `hide-address` and `hide-size` style properties to hide specific visual elements
* `flags` can be specified at map file as well
* `size` property at root level to modify the document size
* `labels` property can go on the left side as well
* Friendly name field (`name`) for sections at the yaml map file to be used instead of ID

### Fixed
* Design issue that was hindering creating linked sections if the area contained breaks
* Bug that repeated the title of the area if the area contained breaks
* Breaks on areas that had empty regions would not perform correctly

## [0.2.0] - 2023-12-14

### Added
* Style overriding by section: each section can have its own style
* Added `links/sections`, which links a section or group of between main and secondary area
* `flags` property for each section
* `opacity` style property that controls the background opacity of a linked section
* `background` style property that controls the document's background
* `grows-up` and `grows-down` flags for sections that draw an arrow indicating the growth direction of the section
* `growth-arrow-weight`, `-stroke-color` and `-fill-color` style properties for the sections growth arrows
* Method at `Style` class to easily override properties from another object: `override_properties_from`
* Method at `Style` class to get a default initialized object: `get_default`
* Property names in yaml also accept `-` instead of underscore
* `style` property to `links`, `area`, and `area/sections`
* flags are specified per section at `area/sections/`
* custom labels at specific memory addresses

### Changed
* Naming of the memory domain: Area instead of Map
* Refactored initialization of `AreaView` to pass parameters via `kwargs`
* Refactored out parameter `dwg` at `map_drawer.py`
* `size-x` and `size-y` moved to `[size]`, `x` and `y` to `[pos]`, `addresses` to `[range]`
* overridden style for areas is constructed before class declaration, and not before drawing

## [0.1.0] - 2023-11-24

* Initial release


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 Raúl Gotor

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# LinkerScope

## Project summary

LinkerScope is a memory map diagram generator. It can be fed either with a GNU Linker map file or with a custom `yaml` file
and generate beautiful and detailed diagrams of the different areas and sections such as the one below.

![Example of_STM32F103 Memory Map generated with LinkerScope](docs/assets/stm32f103_map.svg)

<a href="https://ko-fi.com/K3K010I5L1">
  <img src=".github/kofi_button_blue.png" alt="ko-fi" width="230"/>
</a>

## Installing LinkerScope

Optionally create and activate an environment for LinkerScope:

```bash
python3 -m venv venv
source env/bin/activate
```

Install the needed requirements by executing:

```bash
pip3 install -r requirements.txt
```

## Usage

### Execution

LinkerScope needs a file with memory information for basic functionality:
that could be either GNU Linker map files (`.map`) or custom structured YAML file (`.yaml`).

```bash
./linkerscope.py linker.map [<options>]
```

This will output memory map diagram to a default `map.svg` file. 
While this enables a quick generation of memory map, one might want to carefully tune the
different properties of the diagram such as visual and style properties, memory regions, names, links, ...

For that, a [configuration file](#creating-a-configuration-file) with the desired characteristics has to be specified:

```bash
./linkerscope.py linker.map --config config.yaml --output map.svg
```

where:
- 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.
- `-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.
- `-o, --output` [OPTIONAL] specifies the path to the output file, which will be a newly generated SVG.
- `--convert` [OPTIONAL] tells LinkerScope to perform a conversion from a `.map` file to `.yaml` file containing memory information. After conversion, proqram will quit.


### Input files

LinkerScope can use two types of input files: GNU linker map files (`.map`) or custom defined yaml files (`.yaml`).

#### Using .map files

Under the hood, LinkerScope will convert `.map` files to custom `.yaml` ones, and will work from there.
Since this operation is time-consuming and makes no sense to do it multiple times, two strategies can be
performed when using `.map` files:
- Convert `.map` files to `.yaml` file and then use the `.yaml` file as an input to LinkerScope
  > 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:
  > ```shell
  >  # Conversion step
  > ./linkerscope.py examples/sample_map.map --convert 
  > 
  >  # Map diagram generation. You can execute multiple times without having to do the conversion again
  > ./linkerscope.py map.yaml -c examples/sample_config.yaml -o sample_map.svg 
  >```
- Directly use the `.map` files to output a memory map diagram.
  > Use this strategy when you already have a configuration file, and you know that LinkerScope will produce the expected result. Execute the example below:
  > ```shell
  > ./linkerscope.py examples/sample_map.map -c examples/sample_config.yaml -o sample_map.svg 
  > ```

#### Manually crafted memory map files

Custom 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.
Normally you would do that when representing simple memory maps.

For making a memory map file, one has to specify at least a single section. Each section must include
an `id`, an `address` and a `size`.

While these three are needed, there are other possible attributes that are optional:

- `name`: Friendly text name that would be used instead of the `id`
- `type`: Section type, which can be used for different purposes. Current possibilities are `section` (default) and `area`.

The input file should contain the `map` keyword whose value is an array of sections. Below an example
of how an input file should look like:

```yaml
- address: 0x80045D4
  size:    0x0000F00
  id:      .rodata
  name:    Read Only Data
  type:    area
- address: 0x8002C3C
  id:      SysTick_Handler
  size:    0x0000008
- ...
```

In order to use this file, invoke LinkerScope and specify the yaml map file as input:

```bash
./linkerscope.py memory_map.yaml -o memory_map.svg -c config.yaml
```

### Creating a configuration file

The configuration file is a `.yaml` file containing all the required information to tell
LinkerScope what and how to draw the memory map. All information there is optional, including the
file itself. If this information is not provided, the default values will be used.

Normally, a configuration file contains **areas**, **links** and **style** information.

**Areas** provides a list of memory areas to be displayed, with information regarding its position
and size on the map, memory range to include or specific drawing style.
Additionally, it can contain a **sections** sub-property where specific sections can be added to
modify desired properties

**Links** provides a simple way of graphically relating same memory addresses across different areas

**Style** defines the parent drawing style properties that will be used at the document.
Additionally, each area can override specific style properties at its style section.
Lastly, sections can override parent (area) style as well

```yaml
style:
  background: '#99B898'
  stroke: 'black'
  # ...

areas:
- area:
    style:
    # ...
    title: Register Map
    range: [0x0, 0x100000000]
    sections:
      - names: [USART1, USART2]
        style:
          hide-name: true
    # ...
- area:
    # ...

links:
  addresses: [0x80045d4]
  sections: [__malloc_av_, [TIM2, TIM3]]

```

#### Areas

The diagram can have one or multiple areas. When multiple areas are declared,
first area has a special status since all links will start on it and go to the corresponding sections on the other areas
The areas are declared at root level under `areas`. Then each area must use the key `area`.
Areas can be placed at any position in the document, and some of its properties can be tuned.elements such as labels, memory addresses,
For instance, some elements such as the name / id, size, address, can be show or hidden, labels at
specific memory regions can be configured, sections can be marked as break sections, and the 
visual style of the area and its sections can be modified independently from the others.

For each area, the following characteristics of an area can be defined:
- `title`:  **[Optional, none]**
  - The title of the area, which will appear on top of it
- `pos`:  **[Optional, (50, 50)]** **[x, y]**
  - absolute position  of the area's top-left corner in pixels
- `size`:  **[Optional, (300, 600)]** **[width, height]**
  - area size in pixels
- `range`:  **[Optional, (0, no limit)]** **[min, max]**
  - range of addresses that will be taken into account in this area. 
- `start`: **[start, end]** force area to start in to a given address
- `section-size`: **[Optional, (0, no limit)]** **[min, max]**
  - size range of the sections that should be shown. Exclude others.
- `style`:  **[Optional, default: parent style]**
  - specific style for current area. See [Styles](####Styles) section.
- `sections`: **[Optional, none]**
  - specify or modify a section or group of sections property such as `style`, `flags`,...
    - `names`:
      - list of one or more sections to modify with the parameters below
    - `flags`: **[Optional, none]**
      - flags to append to the specified section/s. See [Flags](#### Section flags) section.
    - `style`: **[Optional, parent style]**
      - style properties to or modify to the specified section/s
- `labels`: **[Optional, none]**
  - Add text labels to specific memory positions of the current area
    - `address`:
      - Memory address where the label should be placed
    - `text`:
      - Text to display for this label
    - `length`: **[Optional, none]**
      - length of the label line in pixels
    - `directions`: **[Optional, none]**
      - direction or list of directions for the arrow head. Possible values are none, `in`, `out` or both.
    - `side`: **[Optional, `right`]**
      - Area side at which the label should be placed
    - `style`: **[Optional, parent style]**
      - style properties to or modify to the specified section/s

Below an example of area definition:

```yaml
areas:
- area:
    title: 'Full Memory Map'
    pos: [30, 50]
    size: [300, 900]
    range: [0x0, 0x100000000]
    section-size: [0x02F00000]
    style:
      fill: *pastel_green
    sections:
    - names: [ External Device ]
      flags: grows-up
      style:
        hide-size: true
```
#### Labels

Labels can bind a text string with a specific memory position. This property falls inside `area`. 
An extract from the `labels` example can be found below:

```yaml
areas:
 - area:
    labels:
      - address: 0xe8043800
        text: In direction
        length: 150
        directions: in
        style:
          stroke-dasharray: '5,1,3,1,3'
     # ...
```

![Example of different labels](docs/assets/labels_map.svg)

> The example can be executed at the root folder by running:
>  ```bash
>  ./linkerscope.py examples/labels_map.yaml -c examples/labels_config.yaml
>  ```

#### Section flags

Section flags allows flagging specified sections with special properties.
These properties are `hidden`, `break`, `grows-up` and `grows-down`.

Flags are listed under property `flags` and can be specified both at the map files under each section

```yaml
# flags can be defined at map.yaml files under each section
map:
- address: 0x20000000
  id: stack
  # ...
  flags: grows-down, break
```

or at the configuration files, with the possibility to specify multiple sections at the same time:

```yaml
# flags can be defined at config.yaml files under each section or group of sections
areas:
- area:
  # ...
  sections:
  - names: [ROM Table, TPIU]
    flags: break
```

##### `hidden`

This flag marks a section or sections to be hidden. Its properties are still computed, the space
that occupies in the graph is still accounted for, but it is not shown. This could be useful, for
instance, to remove irrelevant sections while to some other more important. Another quite useful
use case is doing side by side memory maps:

![Example for side by side maps](docs/assets/side_by_side_map.svg)

> The example can be executed at the root folder by running:
>  ```bash
>  ./linkerscope.py examples/side_by_side_map.yaml  -c examples/side_by_side_config.yaml
>  ```

##### Growths
These flags specify the section as growing section, for instance, if the section is meant to grow into one direction, such as the stack.
When 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:

![Example of the different breaks](docs/assets/stack_map.svg)

> The example can be executed at the root folder by running:
>  ```bash
>  ./linkerscope.py examples/stack_map.yaml -c examples/stack_config.yaml
>  ```

##### `break`

A break or discontinuous section shortens a sized section to a fixed size by drawing a symbol representing a discontinuity across it.
This is specially useful when wanting to include several sections of considerable different sizes in one diagram.
Reducing the size of the biggest one helps to visually simplify the diagram and evenly distribute the space.

There are four different break styles, which can be defined by the 'break-type' style property: `~`: Wave,  `≈`: Double wave, `/`: Diagonal, `...`: Dots

![Example of the different breaks](docs/assets/break_map.svg)

> The example can be executed at the root folder by running:
>  ```bash
>  ./linkerscope.py examples/break_map.yaml -c examples/break_config.yaml
>  ```

#### Links

Links establish a connection between same addresses or sections at different areas.

While address links are represented with a finite line between the addresses, section link drawing
cover the whole region space. These can be used, for instance, to represent a _zoom_ in effects from one overview area
to other area with more detail.

> When drawing a section link, Linkerscope expects both start and end section addresses to be visible at both intended areas.
If any of those is not present, the link will not be drawn

Links are defined at root level of the configuration file under the `links` tag.
Links must have either `addresses` or `sections` tags or both.
- `addresses` is a list of integers representing memory addresses
- `sections` is a list whose elements can be either single section id's, section id's pairs (as a sublist) or both.
  When using pairs, the first element should be the one with the lowest memory address. 
Additionally, specific styles can be specified under the `style` tag.


```yaml
links:
  style:
    stroke: 'lightgray'
    stroke-width: 1
    fill: 'gray'
  addresses: [ 0xe8040000, 0xe8042000 ]
  sections: [['Bit band region', 'Bit band alias'],'ITM']

```
![Example of linked sections](docs/assets/link_map.svg)

> The example can be executed at the root folder by running:
>  ```bash
>  ./linkerscope.py examples/link_map.yaml -c examples/link_config.yaml
>  ```
> 
#### Styles

The style can be defined at document level, where it will be applied to all areas, but also at area or even at section level.
Specifying a style at area level will override the specified properties for the whole area where it was defined.
Specifying it at section level, it will override style only for the specified section or group of sections. 

```yaml
style:
  # This is a style defined at document level
  text-fill: 'lightgrey'
  background: 'black'
  stroke: '#99B898'
  stroke-width: 1
  font-type: 'Helvetica'

areas:
- area:
    title: 'Internal Memory'
    pos: [30, 50]
    style:
      # This is a style defined at area level, which will override fill property only applied at Main Memory area
      fill: 'blue'
    sections:
    - names: [ SRAM, Flash ]
      style:
        # This is a style defined at section level, and will be applied only to SRAM and Flash sections
        fill: 'green'
        hide-address: true
- area:
    title: 'External Memory'
    # ...
```

Below a list of style properties with general use and specific for sections:
##### General
  - `background`, `fill`, `font-size`, `font-type`, `opacity`, `size`, `stroke`, `stroke_dasharray`, `stroke-width`, `text-stroke`,`text-fill`,`text-stroke-width`, `weight`

##### Section properties:
  - `break-type`: specify memory break type. See [`break`](#break) section
  - `break-size`: specify memory break size in pixels. See [`break`](#break) section
  - `growth-arrow-size`: size of the direction growth arrow. See [`Growths`](#growths) section
  - `growth-arrow-fill`: color for the direction growth arrow. See [`Growths`](#growths) section
  - `growth-arrow-stroke`: stroke color for the direction growth arrow. See [`Growths`](#growths) section
  - `hide-size`: hides the size label of a section [`true | auto | false` ]
  - `hide-name`: hides the name label of a section [`true | auto | false` ]
  - `hide-address`: hides the address label of a section [`true | auto | false` ]

#### Other properties

_Document size_

The generated SVG document has a fixed size. If you want to adjust it, use the `size` property at root level to pass
desired document width and height in pixels.

## Run some examples with LinkerScope

At 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.

## Roadmap

- [x] Labels at specific memory addresses
- [x] Area titles
- [x] Links across specific areas
- [x] Choose side of the labels
- [x] Memory direction
- [x] Hide specific elements
- [ ] Memory size in bytes
- [x] Section links across breaks
- [x] Friendly name and identifier
- [ ] Legend
- [ ] Area representation different from section
- [ ] Make `type` default to `section`
- [x] Bug: title appears at top of each break section, if long enough

## References

- [YAML cheatsheet](https://quickref.me/yaml)
- [MapViewer](https://github.com/govind-mukundan/MapViewer)
- [LinkerMapViz](https://github.com/PromyLOPh/linkermapviz)

## License

Distributed under the MIT License. See `LICENSE` for more information.


================================================
FILE: area_view.py
================================================
import copy

from helpers import safe_element_list_get, safe_element_dict_get, DefaultAppValues
from labels import Labels
from logger import logger
from style import Style


class AreaView:
    """
    AreaView provides the container for a given set of sections and the methods to process
    and transform the information they contain into useful data for graphical representation
    """
    pos_y: int
    pos_x: int
    zoom: int
    address_to_pxl: float
    total_height_pxl: int
    start_address: int
    end_address: int

    def __init__(self,
                 sections,
                 style,
                 area_config=[],
                 labels=None,
                 is_subarea = False):
        self.sections = sections
        self.processed_section_views = []
        self.is_subarea = is_subarea
        self.area = area_config
        self.style = style
        self.start_address = safe_element_dict_get(self.area, 'start', self.sections.lowest_memory)
        self.end_address = safe_element_dict_get(self.area, 'end', self.sections.highest_memory)
        self.pos_x = safe_element_list_get(
            safe_element_dict_get(self.area, 'pos'), 0, default=DefaultAppValues.POSITION_X)

        self.pos_y = safe_element_list_get(
            safe_element_dict_get(self.area, 'pos'), 1, default=DefaultAppValues.POSITION_Y)

        self.size_x = safe_element_list_get(
            safe_element_dict_get(self.area, 'size'), 0, default=DefaultAppValues.SIZE_X)

        self.size_y = safe_element_list_get(
            safe_element_dict_get(self.area, 'size'), 1, default=DefaultAppValues.SIZE_Y)

        self.labels = Labels(safe_element_dict_get(self.area, 'labels', []), style)
        self.title = safe_element_dict_get(self.area, 'title', DefaultAppValues.TITLE)
        self.address_to_pxl = (self.end_address - self.start_address) / self.size_y

        if not self.is_subarea:
            self._process()

    def get_split_area_views(self):
        """
        Get current area view split in multiple area views around break sections
        :return: List of AreaViews
        """
        return self.processed_section_views

    def to_pixels(self, value) -> float:
        """
        Convert a given address to pixels in an absolute manner,
        according to the address / pixel size ratio of current area

        :param value: Address to be converted to pixels
        :return: Conversion result
        """
        return value / self.address_to_pxl

    def to_pixels_relative(self, value) -> float:
        """
        Convert a given address to pixels in a relative manner,
        according to the address / pixel size ratio of current area

        Relative in this context means relative to the start address of the Area view. If Area View
        starts at 0x20000 and ends at 0x30000, passing these values to this function for an area
        with a height of 1000 pixels, will result in 0 and 1000 respectively

        :param value: Address to be converted to pixels
        :return: Conversion result
        """
        return self.size_y - ((value - self.start_address) / self.address_to_pxl)

    def _overwrite_sections_info(self):
        """
        Override default style with section specific style

        Overrides default style (normally style defined by the area it is at) and flags information
        on a section given a new definition is provided for an specific section at the map or
        configuration files
        """

        for section in self.sections.get_sections():

            section_style = copy.deepcopy(self.style)
            section.style = section_style

            inner_sections = safe_element_dict_get(self.area, 'sections', [])

            if inner_sections is None:
                logger.warning(
                    "'sections' property is declared but is empty. Field has been ignored")
                inner_sections = []

            for element in inner_sections:

                section_names = safe_element_dict_get(element, 'names', [])

                if section_names is None:
                    logger.warning(
                        "'sections' property is declared but is empty. Field has been ignored")
                    section_names = []

                for item in section_names:
                    if item == section.id:
                        # OVERWRITE style, address, size and type if needed
                        section_style.override_properties_from(Style(style=element.get('style')))
                        section.address = element.get('address', section.address)
                        section.type = element.get('type', section.type)
                        section.size = element.get('size', section.size)
                        # As flags can be defined previously at map file, APPEND whatever is new
                        section.flags += element.get('flags', section.flags)

    def _process(self):
        def recalculate_subarea_size_y(start_mem_addr, end_mem_addr):
            """
            Recalculates the size of the current sub-area, provided the maximum and minimum
            memory that this area has to show

            For that, it makes a relation between the memory that needs to be displayed and
            the total non-break memory available

            :param start_mem_addr: minimum memory address that the new sub-area must show
            :param end_mem_addr: maximum memory address that the new sub-area must show
            :return: Recalculated size for this area
            """

            return (self.to_pixels(end_mem_addr - start_mem_addr) / total_non_breaks_size_y_px) * \
                   (total_non_breaks_size_y_px + expandable_size_px)

        def area_config_clone(configuration, pos_y_px, size_y_px, start_mem_addr, end_mem_addr):
            """
            Clones an area configuration and changes position and size

            :param configuration: Area configuration to clone
            :param pos_y_px: Position in pixels for the new cloned configuration
            :param size_y_px: Size y in pixels of the new cloned configuration
            :param start_mem_addr: minimum memory address that the new sub-area must show
            :param end_mem_addr: maximum memory address that the new sub-area must show
            :return: A new area configuration with the provided configuration and provided parameters
            """
            new_configuration = copy.deepcopy(configuration)
            new_configuration['size'] = [DefaultAppValues.SIZE_X, DefaultAppValues.SIZE_Y]
            if new_configuration.get('pos') is None:
                new_configuration['pos'] = [DefaultAppValues.POSITION_X, DefaultAppValues.POSITION_Y]
            new_configuration['size'][1] = size_y_px
            new_configuration['pos'][1] = pos_y_px - size_y_px
            new_configuration['start'] = start_mem_addr
            new_configuration['end'] = end_mem_addr
            return new_configuration

        self._overwrite_sections_info()

        if len(self.sections.get_sections()) == 0:
            print("Filtered sections produced no results")
            return

        split_section_groups = self.sections.split_sections_around_breaks()

        breaks_count = len(self.sections.filter_breaks().get_sections())
        area_has_breaks = breaks_count >= 1
        breaks_section_size_y_px = self.style.break_size if self.style is not None else 20

        if not area_has_breaks:
            if len(self.sections.get_sections()) == 0:
                logger.error(f"An area view without sections made its through the process. "
                             f"This shouldn't be happening")
                exit(-1)
            self.processed_section_views.append(self)
            return

        total_breaks_size_y_px = self._get_break_total_size_before_transform_px()
        total_non_breaks_size_y_px = self._get_non_breaks_total_size_px(total_breaks_size_y_px)

        # Size gained at the area after flagged sections transformed to breaks
        expandable_size_px = total_breaks_size_y_px - (breaks_section_size_y_px * breaks_count)

        last_area_pos = self.pos_y + self.size_y

        for i, section_group in enumerate(split_section_groups):

            # TODO: ideally, instead of doing this, we should have previously obtained an array
            #       of sub-areas with already start and end values set. That method should live
            #       at area class and not at sections. That is, kill the
            #       `split_sections_around_breaks` method
            if section_group is split_section_groups[0]:
                start_addr = self.start_address
                end_addr = split_section_groups[1].lowest_memory
            elif section_group is split_section_groups[-1]:
                end_addr = self.end_address if self.end_address > section_group.highest_memory else section_group.highest_memory
                start_addr = split_section_groups[-2].highest_memory
            elif section_group.is_break_section_group():
                start_addr = section_group.lowest_memory
                end_addr = section_group.highest_memory
            else:
                start_addr = split_section_groups[i - 1].highest_memory
                end_addr = split_section_groups[i + 1].lowest_memory

            # Assign new calculated size y
            corrected_size_y_px = breaks_section_size_y_px \
                if section_group.is_break_section_group() \
                else recalculate_subarea_size_y(start_addr, end_addr)

            subconfig = area_config_clone(self.area,
                                          last_area_pos,
                                          corrected_size_y_px,
                                          start_addr,
                                          end_addr)

            last_area_pos = subconfig['pos'][1]

            self.processed_section_views.append(AreaView(
                sections=section_group,
                area_config=subconfig,
                labels=self.labels,
                style=self.style,
                is_subarea=True)
            )

    def _get_break_total_size_before_transform_px(self):
        """
        Compute the sum of pixels that the break sections would occupy if they wouldn't be break
        sections
        :return: Computed size in pixels
        """
        total_breaks_size_px = 0

        for _break in self.sections.filter_breaks().get_sections():
            total_breaks_size_px += self.to_pixels(_break.size)

        return total_breaks_size_px

    def _get_non_breaks_total_size_px(self, breaks_size_y_sum_px):
        """
        Get pixel count at y of displayed memory that is not break-flagged section before
        transformation into a break section.

        That takes into account both normal sections and empty memory

        :param breaks_size_y_sum_px: Total pixel count at y axis occupied by break-flagged sections before transformation into break sections.
        :return:
        """

        highest_mem = self.end_address if self.end_address > self.sections.highest_memory else self.sections.highest_memory
        lowest_mem = self.start_address if self.start_address < self.sections.lowest_memory else self.sections.lowest_memory
        return self.to_pixels(highest_mem - lowest_mem) - breaks_size_y_sum_px


================================================
FILE: examples/break_config.yaml
================================================
size: [950, 570]

variables:
  graphite: &graphite '#2A363B'
  pastel_green: &pastel_green '#99B898'

style:
  text-fill: 'black'
  break-size: 60
  #background: *graphite
  fill: *pastel_green
  hide-address: true
  hide-size: true
  stroke: *graphite
  stroke-width: 0
  font_type: 'Helvetica'

areas:
- area:
    title: 'Wave'
    pos: [30, 50]
    style:
      break-type: '~'
    sections:
    - names: [ External RAM ]
      flags: break
    - names: [ External Device, Peripheral, SRAM Area, External RAM ]
      style:
        stroke-width: 1

- area:
    title: 'Double-wave'
    pos: [260, 50]
    style:
      break-type: '≈'
      stroke-width: 1
    sections:
    - names: [ External RAM ]
      flags: break

- area:
    title: 'Diagonal'
    pos: [490, 50]
    style:
      break-type: '/'
    sections:
    - names: [ External RAM ]
      flags: break
    - names: [ External Device, Peripheral, SRAM Area, External RAM ]
      style:
        stroke-width: 1
- area:
    title: 'Dots'
    pos: [720, 50]
    style:
      break-type: '...'
      stroke-width: 1
    sections:
    - names: [ External RAM ]
      flags: break


================================================
FILE: examples/break_map.yaml
================================================
map:
- address: 0x20000000
  size:    0x20000000
  id: SRAM Area
  type: area
- address: 0x40000000
  size:    0x20000000
  id: Peripheral
  type: area
- address: 0x60000000
  size:    0x40000000
  id: External RAM
  type: area
- address: 0xA0000000
  size:    0x40000000
  id: External Device
  type: area

================================================
FILE: examples/labels_config.yaml
================================================
size: [800, 300]
variables:
  graphite: &graphite '#2A363B'
  pastel_green: &pastel_green '#99B898'
  pastel_yellow: &pastel_yellow '#FECEA8'
  pastel_orange: &pastel_orange '#FF847C'
  pastel_red: &pastel_red '#E84A5F'

style:
  text-fill: lightgrey
  background: *graphite
  hide-size: true

areas:
- area:
    title: 'Address Link Example'
    size: [ 200, 200 ]
    pos: [200]
    range: [0xE8040000, 0xE8044000]
    style:
      stroke: grey
      stroke-width: 1
      weight: 1
      text_fill: lightgrey
      stroke-dasharray: '1'

    labels:
      - address: 0xe8043800
        text: In direction
        length: 150
        directions: in
        style:
          stroke-dasharray: '5,1,3,1,3'
      - address: 0xe8042800
        text: No direction
        length: 150
        side: right
        style:
          stroke-dasharray: '2,2'
      - address: 0xe8041800
        text: Out direction
        length: 150
        directions: out
      - address: 0xe8041800
        text: Label at left side
        length: 40
        directions: out
        side: left
      - address: 0xe8040800
        text: In and Out directions
        length: 150
        directions: [in, out]

    sections:
    - names: [ ROM Table ]
      style:
        fill: *pastel_green
    - names: [ External PPB ]
      style:
        fill: *pastel_yellow
        text_fill: grey
    - names: [ ETM, Peripheral ]
      style:
        fill: *pastel_orange
    - names: [ TPIU ]
      style:
        fill: *pastel_red






================================================
FILE: examples/labels_map.yaml
================================================
map:
- address: 0xE8040000
  size:    0x00001000
  id: TPIU
  type: section
- address: 0xE8041000
  size:    0x00001000
  id: ETM
  type: section
- address: 0xE8042000
  size:    0x00001000
  id: External PPB
  type: section
- address: 0xE8043000
  size:    0x00001000
  id: ROM Table
  type: section

================================================
FILE: examples/link_config.yaml
================================================
size: [750, 650]
variables:
  graphite: &graphite '#2A363B'
  pastel_green: &pastel_green '#99B898'
  pastel_yellow: &pastel_yellow '#FECEA8'
  pastel_orange: &pastel_orange '#FF847C'
  pastel_red: &pastel_red '#E84A5F'

style:
  text-fill: 'lightgrey'
  background: *graphite
  fill: '#99B898'
  hide-size: true
  stroke: *graphite
  stroke-width: 1
  text-stroke: 'black'
  text-stroke-width: 0
  font-type: 'Helvetica'
  break-type: '~'
  break-size: 100

areas:
- area:
    title: 'Full Memory Map'
    size: [200, 500]
    range: [0x0, 0x100000000]
    section-size: [0x02F00000]
    style:
      hide-address: true
      fill: *pastel_green
    sections:
    - names: [ External Device, External Ram ]
      fill: '#FF847C'
      style:
        hide-size: true
        hide-address: true

- area:
    title: 'Address Link Example'
    pos: [ 400, 50 ]
    size: [ 200, 200 ]
    range: [0xE8040000, 0xE8044000]
    sections: # Adding the 'regions' sub-level under 'style'
    - names: [ ROM Table ]
      style:
        fill: *pastel_green
    - names: [ External PPB ]
      style:
        fill: *pastel_yellow
    - names: [ ETM, Peripheral ]
      flags: break
      style:
        fill: *pastel_orange
    - names: [ TPIU ]
      style:
        fill: *pastel_red

- area:
    title:
    pos: [ 400, 330 ]
    size: [ 200, 300 ]
    range: [0x40000000, 0x43000000]
    sections: # Adding the 'regions' sub-level under 'style'
    - names: [ Bit ]
      flags: break
      style:
        fill: *pastel_yellow
    - names: [ Bit band region ]
      style:
        fill: *pastel_orange
    - names: [ Reserved ]
      flags: grows-down

links:
  style:
    stroke: 'lightgray'
    stroke-width: 1
    fill: 'gray'
  addresses: [ 0xe8040000 ]
  sections: [['Bit band region', 'Bit band alias'], ['TPIU', 'External PPB']]





================================================
FILE: examples/link_map.yaml
================================================
map:
- address: 0x20000000
  size:    0x20000000
  id: SRAM Area
  type: area
- address: 0x40000000
  size:    0x20000000
  id: Peripheral
  type: area
- address: 0x60000000
  size:    0x40000000
  id: External RAM
  type: area
- address: 0xA0000000
  size:    0x40000000
  id: External Device
  type: area
- address: 0xE0000000
  size:    0x08000000
  id: Private Periph. Bus - Internal
  type: area
- address: 0xE8000000
  size:    0x08000000
  id: Private Periph. Bus - External
  type: area
- address: 0xF0000000
  size:    0x08000000
  id: Vendor Specific
  type: area

- address: 0xE8040000
  size:    0x00001000
  id: TPIU
  type: section
- address: 0xE8041000
  size:    0x00001000
  id: ETM
  type: section
- address: 0xE8042000
  size:    0x00001000
  id: External PPB
  type: section
- address: 0xE8043000
  size:    0x00001000
  id: ROM Table
  type: section

- address: 0xE0000000
  size:    0x00010000
  id: ITM
  type: section
- address: 0xE0010000
  size:    0x00010000
  id: DWT
  type: section
- address: 0xE0020000
  size:    0x00010000
  id: FPB
  type: section
- address: 0xE0030000
  size:    0x000B0000
  id: Reserved
  type: section
- address: 0xE00E0000
  size:    0x00010000
  id: NVIC
  type: section
- address: 0xE00F0000
  size:    0x00010000
  id: NVIC
  type: section

- address: 0x20000000
  size:    0x00100000
  id: Bit band region
  type: section
- address: 0x20100000
  size:    0x01F00000
  id: Bit
  type: section
- address: 0x22000000
  size:    0x01000000
  id: Bit band alias
  type: section

- address: 0x40000000
  size:    0x00100000
  id: Bit band region
  type: section
- address: 0x40100000
  size:    0x01F00000
  id: Bit
  type: section
- address: 0x42000000
  size:    0x01000000
  id: Bit band alias
  type: section

================================================
FILE: examples/sample_config.yaml
================================================
size: [500,1100]

variables:
  graphite: &graphite '#212b38'
  cleargraphite: &cleargraphite '#37465b'
  mydarkgreen: &mydarkgreen '#08c6ab'

style:
  text-fill: 'white'
  background: *graphite
  fill: *mydarkgreen
  hide-address: false
  stroke: *cleargraphite
  stroke-width: 0.5
  text-stroke-width: 0.1
  font-size: 12
  font_type: 'Helvetica'

areas:
- area:
    title: ESP32 app space (extract)
    range: [0x400d0000, 0x400da000]
    section-size: [0x000010,0x0008000]
    pos: [ 100, 50 ]
    size: [200, 1000]


================================================
FILE: examples/stack_config.yaml
================================================
size: [300, 600]

variables:
  green:  &green '#47b39d'
  yellow: &yellow '#ffc153'
  orange: &orange '#eb6b56'
  granat: &granat '#b05f6d'
  purple: &purple '#462446'
  graphite: &graphite '#313b48'

style:

  hide-size: true
  hide-address: true
  growth-arrow-size: 2
  stroke-width: 0

areas:
- area:
    style:
        background: *graphite
    title: 'Full Memory Map'
    sections:
      - names: [Text]
        style:
          fill: *green
      - names: [ Initialized data ]
        style:
          fill: *yellow
      - names: [Uninitialized data]
        style:
          fill: *orange
      - names: [ Heap ]
        flags: grows-up
        style:
          fill: *granat
      - names: [ Stack ]
        flags: grows-down
        style:
          fill: *purple
          text-fill: white

================================================
FILE: examples/stack_map.yaml
================================================
map:
- address: 0x20000000
  size:    0x10000000
  id: Text
  type: area
- address: 0x30000000
  size:    0x10000000
  id: Initialized data
  type: area
- address: 0x40000000
  size:    0x10000000
  id: Uninitialized data
  type: area
- address: 0x50000000
  size:    0x10000000
  id: Heap
  type: area
  # flags: grows-up
- address: 0xA0000000
  size:    0x10000000
  id: Stack
  type: area
  #flags: grows-down


================================================
FILE: examples/stm32f103_config.yaml
================================================
size: [1100,1100]

variables:
  graphite: &graphite '#212b38'
  cleargraphite: &cleargraphite '#37465b'
  myturqoise: &myturqoise '#4aefd7'
  mydarkgreen: &mydarkgreen '#08c6ab'
  mypurple: &mypurple '#726eff'

  #mylightblue: &mylightblue '#7bd5f5'
  #myblue: &myblue '#1ca7ec'
  #mydarkblue: &mydarkblue '#1f2f98'
  mylightgrey: &mylightgrey '#F6F6F6'

style:
  text-fill: 'white'
  break-size: 25
  break-type: '≈'
  growth-arrow-size: 2
  growth-arrow-fill: 'white'
  growth-arrow-stroke: 'black'
  background: *graphite
  fill: *mydarkgreen
  hide-size: true
 # stroke: *mydarkblue
  stroke-width: 0
  text-stroke: 'black'
  text-stroke-width: 0.1
  font-size: 12
  font_type: 'Helvetica'

areas:
- area:
    title: STM32F103 Memory Space
    range: [0x0, 0x100000000]
    pos: [ 100, 50 ]
    size: [200, 700]
    style:
      hide-size: true

    sections:
      - names: [none]
        style:
          hide-name: true
          fill: *cleargraphite

- area:
    range: [0x08000000, 0x0801FFFF]
    size: [200, 100]
    pos: [450, 700]
    style:
      hide-size: false
      fill: *myturqoise
    sections:
      - names: [Flash Memory]
        style:
          fill: *mypurple
- area:
    range: [0x1FFFF000, 0x1FFFF80F]
    size: [200, 100]
    pos: [450, 550]
    style:
      hide-size: false
    sections:
      - names: [System Memory]
        style:
          fill: *myturqoise
- area:
    range: [0xE0000000, 0xE1000000]
    size: [200, 200]
    pos: [450, 50]
    style:
      hide-size: false
    sections:
      - names: [ M3 Cortex Internal Peripherals ]

- area:
    title: APB memory space
    range: [0x40000000, 0x40030000]
    pos: [ 750, 50 ]
    size: [ 200, 1000 ]

    sections:
      - names: [Reserved1, Reserved2, Reserved3, Reserved4, Reserved5, Reserved6, Reserved7, Reserved8, Reserved9, Reserved10, Reserved11]
        flags: break
        style:
          fill: *cleargraphite
      - names: [AFIO, EXTI, PORT A, PORT B, PORT C, PORT D, PORT E ]
        style:
          fill: *mypurple


links:
  style:
    opacity: 0.2
    fill: *mylightgrey
    stroke: lightgrey
  sections: [[TIM2, CRC], M3 Cortex Internal Peripherals, Flash Memory, System Memory]




================================================
FILE: examples/stm32f103_map.yaml
================================================
map:
  - id: Aliased
    address: 0x00000000
    size:    0x08000000
    type: area

  - id: Flash Memory
    address: 0x08000000
    size:    0x0001FFFF
    type: area
  - id: Reserved0
    address: 0x0801FFFF
    size:    0x1F7FD001
    type: area
  - id: System Memory
    address: 0x1FFFF000
    size:    0x00000800
    type: area
  - id: Option Bytes
    address: 0x1FFFF800
    size:    0x0000000F
    type: area
  - id: Reserved00
    address: 0x1FFFF80F
    size:    0x00000800
    type: area


  - id:      none
    address: 0x00000000
    size:    0x20000000
    type:    area
  - id:      none
    address: 0x20000000
    size:    0x20000000
    type:    area
  - id:      none
    address: 0x40000000
    size:    0x20000000
    type:    area
  - id:      none
    address: 0x60000000
    size:    0x20000000
    type:    area
  - id:      none
    address: 0x80000000
    size:    0x20000000
    type:    area
  - id:      none
    address: 0xA0000000
    size:    0x20000000
    type:    area
  - id:      none
    address: 0xC0000000
    size:    0x20000000
    type:    area



  - id:      none
    address: 0xE0100000
    size:    0x0FE00000
    type:    area

  - id:      SRAM
    address: 0x20000000
    size:    0x08000000
    type:    area

  - id:      Peripherals
    address: 0x40000000
    size:    0x08000000
    type:    area

  - id:      M3 Cortex Internal Peripherals
    address: 0xE0000000
    size:    0x01000000
    type:    area

  - id:      TIM2
    address: 0x40000000
    size:    0x00000400
    type:    area
  - id:      TIM3
    address: 0x40000400
    size:    0x00000400
    type:    area
  - id:      TIM4
    address: 0x40000800
    size:    0x00000400
    type:    area

  - id: Reserved1
    address: 0x40000C00
    size:    0x1c00
    type:    area

  - id:      RTC
    address: 0x40002800
    size:    0x00000400
    type:    area
  - id:      WWDG
    address: 0x40002C00
    size:    0x00000400
    type:    area
  - id:      IWDG
    address: 0x40003000
    size:    0x00000400
    type:    area

  - id: Reserved2
    address: 0x40003400
    size:    0x00000400
    type:    area

  - id:      SPI2
    address: 0x40003800
    size:    0x00000400
    type:    area

  - id: Reserved3
    address: 0x40003C00
    size:    0x00000800
    type:    area

  - id:      USART2
    address: 0x40004400
    size:    0x00000400
    type:    area
  - id:      USART3
    address: 0x40004800
    size:    0x00000400
    type:    area

  - id: Reserved4
    address: 0x40004C00
    size:    0x00000800
    type:    area

  - id:      I2C1
    address: 0x40005400
    size:    0x00000400
    type:    area
  - id:      I2C2
    address: 0x40005800
    size:    0x00000400
    type:    area
  - id:      USB Registers
    address: 0x40005C00
    size:    0x00000400
    type:    area
  - id:      USB/CAN SRAM
    address: 0x40006000
    size:    0x00000400
    type:    area
  - id:      BXCAN
    address: 0x40006400
    size:    0x00000400
    type:    area

  - id: Reserved5
    address: 0x40006800
    size:    0x00000400
    type:    area

  - id:      BKP
    address: 0x40006C00
    size:    0x00000400
    type:    area
  - id:      PWR
    address: 0x40007000
    size:    0x00000400
    type:    area

  - id: Reserved6
    address: 0x40007400
    size:    0x00008C00
    type:    area

  - id:      AFIO
    address: 0x40010000
    size:    0x00000400
    type:    area
  - id:      EXTI
    address: 0x40010400
    size:    0x00000400
    type:    area
  - id:      PORT A
    address: 0x40010800
    size:    0x00000400
    type:    area
  - id:      PORT B
    address: 0x40010C00
    size:    0x00000400
    type:    area
  - id:      PORT C
    address: 0x40011000
    size:    0x00000400
    type:    area
  - id:      PORT D
    address: 0x40011400
    size:    0x00000400
    type:    area
  - id:      PORT E
    address: 0x40011800
    size:    0x00000400
    type:    area

  - id: Reserved7
    address: 0x40011C00
    size:    0x00000800
    type:    area


  - id:      ADC1
    address: 0x40012400
    size:    0x00000400
    type:    area
  - id:      ADC2
    address: 0x40012800
    size:    0x00000400
    type:    area
  - id:      TIM1
    address: 0x40012C00
    size:    0x00000400
    type:    area
  - id:      SPI1
    address: 0x40013000
    size:    0x00000400
    type:    area

  - id: Reserved7
    address: 0x40013400
    size:    0x00000400
    type:    area

  - id:      USART1
    address: 0x40013800
    size:    0x00000400
    type:    area

  - id: Reserved8
    address: 0x40013C00
    size:    0x0000C400
    type:    area

  - id:      DMA
    address: 0x40020000
    size:    0x00000400
    type:    area

  - id: Reserved9
    address: 0x40020400
    size:    0x00000C00
    type:    area

  - id:      RCC
    address: 0x40021000
    size:    0x00000400
    type:    area

  - id: Reserved10
    address: 0x40021400
    size:    0x00000C00
    type:    area

  - id:      Flash Interface
    address: 0x40022000
    size:    0x00000400
    type:    area

  - id: Reserved11
    address: 0x40022400
    size:    0x00000C00
    type:    area

  - id:      CRC
    address: 0x40023000
    size:    0x00000400
    type:    area





================================================
FILE: gnu_linker_map_parser.py
================================================
import re
import yaml

from section import Section


class GNULinkerMapParser:
    """
    Parse a GNU linker map file and convert it to a yaml file for further processing
    """
    def __init__(self, input_filename, output_filename):
        self.sections = []
        self.subsections = []
        self.input_filename = input_filename
        self.output_filename = output_filename

    def parse(self):
        with open(self.input_filename, 'r', encoding='utf8') as file:

            file_iterator = iter(file)
            prev_line = next(file_iterator)
            for line in file_iterator:
                self.process_areas(prev_line)
                multiple_line = prev_line + line
                self.process_sections(multiple_line)
                prev_line = line

        my_dict = {'map': []}
        for section in self.sections:
            my_dict['map'].append({
                'type': 'area',
                'address': section.address,
                'size': section.size,
                'id': section.id,
                'flags': section.flags
            })

        for subsection in self.subsections:
            my_dict['map'].append({
                'type': 'section',
                'parent': subsection.parent,
                'address': subsection.address,
                'size': subsection.size,
                'id': subsection.id,
                'flags': subsection.flags
            })

        with open(self.output_filename, 'w', encoding='utf8') as file:
            yaml_string = yaml.dump(my_dict)
            file.write(yaml_string)

    def process_areas(self, line):
        pattern = r'([.][a-z]{1,})[ ]{1,}(0x[a-fA-F0-9]{1,})[ ]{1,}(0x[a-fA-F0-9]{1,})\n'

        p = re.compile(pattern)
        result = p.search(line)

        if result is not None:
            self.sections.append(Section(parent=None,
                                         id=result.group(1),
                                         address=int(result.group(2), 0),
                                         size=int(result.group(3), 0),
                                         _type='area'
                                         )
                                 )

    def process_sections(self, line):
        pattern = r'\s(.[^.]+).([^. \n]+)[\n\r]\s+(0x[0-9a-fA-F]{16})\s+' \
                  r'(0x[0-9a-fA-F]+)\s+[^\n]+[\n\r]{1}'

        p = re.compile(pattern)
        result = p.search(line)

        if result is not None:
            self.subsections.append(Section(parent=result.group(1),
                                            id=result.group(2),
                                            address=int(result.group(3), 0),
                                            size=int(result.group(4), 0),
                                            _type='section'
                                            )
                                    )


================================================
FILE: helpers.py
================================================
from logger import logger


class DefaultAppValues:
    DOCUMENT_SIZE = (400, 700)
    POSITION_X = 50
    POSITION_Y = 50
    SIZE_X = 200
    SIZE_Y = 500
    TITLE = ''

def safe_element_list_get(_list: [], index: int, default=None) -> int:
    """
    Get an element from a list checking if both the list and the element exist

    :param default: Default value to return if list or element are non existent
    :param _list: List to extract the element from
    :param index: Index of the element in the list
    :return: The expected element if exists, None if it doesn't
    """

    return _list[index] if _list is not None and len(_list) > index else default


def safe_element_dict_get(_dict: {}, key: str, default=None) -> int:
    """
    Get an element from a dict checking if both the dict and the element exist

    :param default: Default value to return if list or element are non existent
    :param _dict: Dict to extract the element from
    :param key: Key of the key - value pair to extract
    :return: The expected element if exists, None if it doesn't
    """

    return _dict[key] if _dict is not None and key in _dict else default

================================================
FILE: labels.py
================================================
import copy
from dataclasses import dataclass
from style import Style


@dataclass
class Labels:
    """
    Container for labels, and methods to build them from a yaml label specification
    """
    sections: []
    addresses: []
    style: Style

    def __init__(self, labels, style):
        self.style = style
        self.labels = self.build_labels(labels)

    def build_labels(self, labels_yaml) -> []:
        """
        Build a list of labels (`[Label]`) from a list of labels in a yaml format
        :param labels_yaml: List of labels in a yaml format
        :return: list of labels (`[Label]`)
        """
        labels = []

        for element in labels_yaml:
            style = copy.deepcopy(self.style)
            label = Label(style.override_properties_from(Style(element.get('style'))))

            for key, value in element.items():
                if key != 'style':
                    setattr(label, key.replace('-','_'), value)

            labels.append(label)

        return labels


class Side:
    RIGHT = 'right'
    LEFT = 'left'


@dataclass
class Label:
    """
    Stores single label information for a given address.
    Additionally, provides style information for drawing the link
    """
    def __init__(self, style):
        self.style = style
        self.address = 0
        self.text = 'Label'
        self.length = 20
        self.directions = []
        self.side = Side.RIGHT


================================================
FILE: linkerscope.py
================================================
#!/usr/bin/env python3

import argparse
import copy

import yaml

from area_view import AreaView
from helpers import safe_element_list_get, safe_element_dict_get, DefaultAppValues
from links import Links
from logger import logger
from map_render import MapRender
from style import Style
from map_file_loader import MapFileLoader
from sections import Sections


def parse_arguments():
    parser = argparse.ArgumentParser()
    parser.add_argument('input',
                        help='Name of the map file,'
                             'can be either linker .map files or .yaml descriptor')
    parser.add_argument('--output',
                        '-o',
                        help='Name for the generated .svg file',
                        default='map.svg')
    parser.add_argument('--convert',
                        help='Performs the conversion of a .map file to .yaml if a .map file was passed without any additional step',
                        action='store_true',
                        default=False,
                        required=False
                        )
    parser.add_argument('--config',
                        '-c',
                        help='Configuration file (.yml). If not specified,'
                             'will use config.yaml as default',
                        )

    return parser.parse_args()


def get_area_views(_raw_sections, _base_style, config=None):
    """
    Get the area view/s with the specified style and properties (if any)

    Given a list of sections, base style and configuration, this function produce a series of
    area views with specific properties and styles. If a configuration object is passed, it will
    be used to retrieve additional configured style and properties for one or more area views.
    If no configuration is passed, only one area view will be generated with the default style
    and properties

    :param _raw_sections: A list of unprocessed sections to be selected from and displayed
    :param _base_style: Base / default style to build child styles from
    :param config: Optional, configuration object indicating number of areas, style, properties,...
    :return: A list of configured area views
    """
    def get_default_area_view(sections, style):
        """
        Get an area view configured with default parameters
        :param sections: A list of unprocessed sections to be selected from and displayed
        :param style: Base / default style to build child styles from
        :return: List of one element corresponding to a default area view
        """
        return [AreaView(
            sections=(Sections(sections=sections)),
            style=copy.deepcopy(style)
        )]

    def get_custom_area_views(sections, style):
        """
        Get a list of area views configured according to passed parameters

        :param sections: A list of unprocessed sections to be selected from and displayed
        :param style: Base / default style to build child styles from
        :return: List of one or various custom area views
        """
        area_views = []
        for i, area_element in enumerate(area_configurations):
            area_config = safe_element_dict_get(area_element, 'area')
            section_size = safe_element_dict_get(area_config, 'section-size', None)
            memory_range = safe_element_dict_get(area_config, 'range', None)
            area_style = copy.deepcopy(style)
            filtered_sections = (Sections(sections=copy.deepcopy(sections))
                                 .filter_address_min(safe_element_list_get(memory_range, 0))
                                 .filter_address_max(safe_element_list_get(memory_range, 1))
                                 .filter_size_min(safe_element_list_get(section_size, 0))
                                 .filter_size_max(safe_element_list_get(section_size, 1))
                                 )
            if len(filtered_sections.get_sections()) == 0:
                logger.warning(f"Filter for area view with index {i} doesn't result in any"
                               f"section. Try re-adjusting memory range, size, ... This area "
                               f"will be omitted")
                continue

            area_views.append(
                AreaView(
                    sections=filtered_sections,
                    area_config=area_config,
                    style=area_style.override_properties_from(
                        Style(style=safe_element_dict_get(area_config, 'style', None)))
                )
            )

        return area_views

    area_configurations = safe_element_dict_get(config, 'areas', []) or []

    if len(area_configurations) == 0:
        return get_default_area_view(_raw_sections, _base_style)
    else:
        return get_custom_area_views(_raw_sections, _base_style)


arguments = parse_arguments()
raw_sections = MapFileLoader(arguments.input, arguments.convert).parse()
base_style = Style().get_default()


links = None
document_size = DefaultAppValues.DOCUMENT_SIZE
configuration = {}

# Apply custom configuration if configuration file is available
if arguments.config:
    with open(arguments.config, 'r', encoding='utf-8') as file:
        configuration = yaml.safe_load(file)
        if configuration is None:
            configuration = {}

    base_style_cpy = copy.deepcopy(base_style)
    style_config = safe_element_dict_get(configuration, 'style', None)
    base_style.override_properties_from(Style(style=style_config))
    yaml_links = safe_element_dict_get(configuration, 'links', None)
    links_style = base_style_cpy.override_properties_from(
        Style(style=safe_element_dict_get(yaml_links,
                                          'style', None)))

    links = Links(yaml_links, style=links_style)
    document_size = safe_element_dict_get(configuration, 'size', DefaultAppValues.DOCUMENT_SIZE)

MapRender(area_view=get_area_views(raw_sections, base_style, configuration),
          links=links,
          style=base_style,
          file=arguments.output,
          size=document_size
          ).draw()


================================================
FILE: links.py
================================================
import logging

from helpers import safe_element_dict_get
from style import Style
from logger import CustomFormatter, logger


class Links:
    """
    Stores the link information between given section or address
    Additionally, provides style information for drawing the link
    """
    sections: []
    addresses: []
    style: Style

    def __init__(self, links=None, style=None):
        self.links = links
        self.addresses = safe_element_dict_get(self.links, 'addresses', [])
        self.sections = safe_element_dict_get(self.links, 'sections', [])
        self.style = style
        self.configuration_validator()

    def configuration_validator(self):

        if self.addresses is None:
            logger.warning("'addresses' property is declared but is empty. Field has been ignored")
            self.addresses = []

        if self.sections is None:
            logger.warning("'sections' property is declared but is empty. Field has been ignored")
            self.sections = []

        for address in self.addresses:

            if not isinstance(address, int):
                self.addresses.remove(address)
                logger.warning(f"Link address '{address}' is incorrect: can only be of the type "
                               f"integer. It will be ignored")

        for section_address in self.sections:
            if not isinstance(section_address, str) and not isinstance(section_address, list):
                self.sections.remove(section_address)
                logger.warning(f"Section link '{section_address}' is incorrect: can only be of the "
                               f"type integer or list. It will be ignored")

            elif isinstance(section_address, list) and len(section_address) != 2:
                self.sections.remove(section_address)
                logger.warning(f"Section link list '{section_address}' can only have exactly two "
                               f"sections. It will be ignored")

            elif isinstance(section_address, list) and \
                    (not isinstance(section_address[0], str) or
                     not isinstance(section_address[1], str)):

                self.sections.remove(section_address)
                logger.warning(f"Section link list elements'{section_address}' must be strings. "
                               f"They will be ignored")


================================================
FILE: logger.py
================================================
import logging


# Adaptation from https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output
class CustomFormatter(logging.Formatter):

    grey = "\x1b[38;20m"
    yellow = "\x1b[33;20m"
    red = "\x1b[31;20m"
    bold_red = "\x1b[31;1m"
    reset = "\x1b[0m"
    format = "%(asctime)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"

    FORMATS = {
        logging.DEBUG: grey + format + reset,
        logging.INFO: grey + format + reset,
        logging.WARNING: yellow + format + reset,
        logging.ERROR: red + format + reset,
        logging.CRITICAL: bold_red + format + reset
    }

    def format(self, record):
        log_fmt = self.FORMATS.get(record.levelno)
        formatter = logging.Formatter(log_fmt)
        return formatter.format(record)


logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
handler.setFormatter(CustomFormatter())
logger.addHandler(handler)


================================================
FILE: map_file_loader.py
================================================
import os
import sys
import yaml
from logger import logger
from section import Section
from gnu_linker_map_parser import GNULinkerMapParser


class MapFileLoader:
    """
    Takes input file provided by user and loads it in memory for further processing.
    Depending on the type of file (.map or .yaml) will include an additional conversion step and
    create a temporary .yaml file
    """
    def __init__(self, file, convert):
        self.input_filename = file
        self.convert = convert

    def parse(self):
        _, file_extension = os.path.splitext(self.input_filename)

        if file_extension == '.map':
            self.parse_map(self.input_filename)
            if self.convert:
                logger.info(".map file converted and saved as map.yaml")
                exit(0)
            return self.parse_yaml('map.yaml')

        if file_extension in ['.yaml', '.yml']:
            if self.convert:
                logger.error("--convert flag requires a .map file")
                exit(-1)
            return self.parse_yaml(self.input_filename)

        logger.error(f"Wrong map file extension: '{file_extension}'. Use .map or .yaml files")
        sys.exit(-1)

    @staticmethod
    def parse_yaml(filename):
        sections = []

        with open(filename, 'r', encoding='utf-8') as file:
            y = yaml.safe_load(file)

        for element in y['map']:
            sections.append(Section(address=element['address'],
                                    size=element['size'],
                                    id=element['id'],
                                    name=element.get('name'),
                                    parent=element.get('parent', 'none'),
                                    _type=element.get('type', 'area'),
                                    flags=element.get('flags', '')
                                    )
                            )

        return sections

    @staticmethod
    def parse_map(input_filename):
        GNULinkerMapParser(input_filename=input_filename, output_filename='map.yaml').parse()


================================================
FILE: map_render.py
================================================
from math import cos
from svgwrite import Drawing
import svgwrite

from helpers import DefaultAppValues
from labels import Side
from logger import logger
from section import Section
from style import Style


class MapRender:
    """
    This class does the actual rendering of the map.

    Takes all the graphical information stored at the different sections and areas, together
    with their style and configuration, and convert them to SVG objects (see `draw()` function)
    """
    dwg: Drawing
    pointer_y: int

    def __init__(self, area_view, links, file='map.svg', size=DefaultAppValues.DOCUMENT_SIZE, **kwargs):
        self.style = kwargs.get('style')
        self.type = type
        self.area_views = area_view
        self.current_style = Style()
        self.links = links
        self.links_sections = self._get_valid_linked_sections(links.sections) if links is not None else []
        self.file = file
        self.size = size
        self.dwg = svgwrite.Drawing(file,
                                    profile='full',
                                    size=self.size
                                    )

    def _get_valid_linked_sections(self, linked_sections):
        """
        Get a valid list of linked sections to draw, given a list of wished sections to be linked

        For a link to be valid, the starting and ending addresses of the linked section/s must be
        visible and available inside of at least one single area

        :param linked_sections: List of sections or pair of sections to be linked
        :return: List of valid (start, end) addresses for sections
        """

        l_sections = []

        # Iterate through all linked sections
        for linked_section in linked_sections:
            appended = False
            multi_section = False

            # Check if we are dealing with a link for a single section or for many of them.
            # That is, user passed a string or a list of two strings
            if isinstance(linked_section, list):
                multi_section = True

            # Iterate through all available areas checking if this is a valid link: i.e, the
            # starting and ending addresses of the linked section/s is visible and available
            # inside of a single area
            for area in self.area_views:
                start = None
                end = None

                # Exit loop if we found that the link is valid
                if appended:
                    break

                for section in area.sections.get_sections():
                    # If single section, the start and end address of the linked section equals
                    # those of the section
                    if not multi_section:
                        if section.id == linked_section:
                            l_sections.append([section.address, section.address + section.size])
                            appended = True
                            break
                    # If multiple section, the start and end address of the linked section are the
                    # start of the first provided section and the end of the second provided section
                    # respectively
                    else:
                        if section.id == linked_section[0]:
                            start = section.address
                        elif section.id == linked_section[1]:
                            end = section.address + section.size

                        # If before finishing the iteration on this area, we found a valid start and
                        # end address, we can append this linked section to the list
                        if start is not None and end is not None:
                            l_sections.append([start, end])
                            appended = True
                            break

                # If we finish iterating the area, and we have a valid start (or end) address but
                # the section was not appended, means that the other end of the section is at
                # another area, and that is not valid
                if multi_section and not appended and (start is not None or end is not None):
                    logger.warning("A multisection zoom region was specified for two sections"
                                   f"of different areas, which is not supported: "
                                   f"{linked_section[0]}, {linked_section[1]}")
                    break

        return l_sections

    def draw(self):

        dwg = self.dwg

        def _draw_area(area) -> svgwrite.container.Group:
            """
            Draw given area

            Draw the title for the area, then, for each subarea proceed to draw
            the different elements. Those are the frame and sections, with its
            information such as labels, name, memory, etc...

            :param area: Area to be drawn
            :return Container with an area to be drawn
            """
            area_group = dwg.g()
            title = self._make_title(area)
            title.translate(area.pos_x, area.pos_y)
            area_group.add(title)

            for sub_area in area.get_split_area_views():
                subarea_group = dwg.g()

                subarea_group.add(self._make_main_frame(sub_area))

                for section in sub_area.sections.get_sections():
                    if section.is_hidden():
                        continue
                    self._make_section(subarea_group, section, sub_area)

                subarea_group.translate(sub_area.pos_x, sub_area.pos_y)

                area_group.add(subarea_group)

            return area_group

        def draw_section_links() -> svgwrite.container.Group:
            linked_sections_group = dwg.g()
            for section_link in self.links_sections:
                is_drawn = False
                for _area_view in self.area_views[1:]:
                    if section_link[0] >= _area_view.sections.lowest_memory and \
                            section_link[1] <= _area_view.sections.highest_memory and \
                            section_link[0] >= self.area_views[0].sections.lowest_memory and \
                            section_link[1] <= self.area_views[0].sections.highest_memory:
                        linked_sections_group.add(self._make_poly(_area_view,
                                                                  section_link[0],
                                                                  section_link[1],
                                                                  self.links.style))
                        is_drawn = True
                        break
                if not is_drawn:
                    logger.warning(f"Starting or ending point of the zoom region is outside the "
                                   f"shown areas for the link with addresses "
                                   f"[{hex(section_link[0])}, {hex(section_link[1])}]")

            return linked_sections_group

        def draw_labels() -> svgwrite.container.Group:
            global_labels = dwg.g()
            for area in self.area_views:
                for subarea in area.get_split_area_views():
                    g = dwg.g()

                    if subarea.labels is not None:
                        for label in subarea.labels.labels:

                            if subarea.sections.has_address(label.address):
                                g.add(self._make_label(label, subarea))

                    g.translate(subarea.pos_x, subarea.pos_y)
                    global_labels.add(g)
            return global_labels

        def draw_growths() -> svgwrite.container.Group:
            # We need to do another pass once all areas are drawn in order to be able to properly
            # draw the growth arrows without the break areas hiding them. Also, as we do stuff
            # outside the loop where the areas are drawn, we loose the reference for translation,
            # and we have to manually translate the grows here
            for _area_view in self.area_views:
                for subarea in _area_view.get_split_area_views():

                    area_growth = dwg.g()
                    for section in subarea.sections.get_sections():
                        if section.is_hidden():
                            continue
                        area_growth.add(self._make_growth(section))

                    area_growth.translate(subarea.pos_x, subarea.pos_y)
                    growths_group.add(area_growth)
            return growths_group

        def draw_links() -> svgwrite.container.Group:
            lines_group = dwg.g()
            for address in self.links.addresses:
                lines_group.add(self._make_link(address, self.links.style))
            return lines_group

        dwg.add(dwg.rect(insert=(0, 0),
                         size=('100%', '100%'),
                         rx=None,
                         ry=None,
                         fill=self.style.background))

        growths_group = dwg.g()

        dwg.add(draw_section_links()) if self.links_sections is not None else None
        dwg.add(draw_links()) if self.links is not None else None

        for area_view in self.area_views:
            dwg.add(_draw_area(area_view))

        dwg.add(draw_labels())
        dwg.add(draw_growths())
        dwg.save()

    def _make_title(self, area_view):
        title_pos_x = area_view.size_x / 2
        title_pos_y = -20
        return self._make_text(area_view.title,
                               (title_pos_x, title_pos_y),
                               style=area_view.style,
                               anchor='middle',
                               text_type='title'
                               )

    def _make_growth(self, section: Section) -> svgwrite.container.Group:
        """
        Make the growth arrows for the sections that have it
        :param section: Section for which to draw the arrow
        :return: A SVG group containing the new arrows
        """
        group = self.dwg.g()
        # Why grows doesn't draw on a break section?
        multiplier = section.style.growth_arrow_size
        mid_point_x = (section.pos_x + section.size_x) / 2
        arrow_head_width = 5 * multiplier
        arrow_head_height = 10 * multiplier
        arrow_length = 10 * multiplier
        arrow_tail_width = 1 * multiplier

        def _make_growth_arrow_generic(arrow_start_y, direction):
            points_list = [(mid_point_x - arrow_tail_width, arrow_start_y),
                           (mid_point_x - arrow_tail_width,
                            arrow_start_y - direction * arrow_length),
                           (mid_point_x - arrow_head_width,
                            arrow_start_y - direction * arrow_head_height),
                           (mid_point_x,
                            arrow_start_y - direction * (arrow_length + arrow_head_height)),
                           (mid_point_x + arrow_head_width,
                            arrow_start_y - direction * arrow_head_height),
                           (mid_point_x + arrow_tail_width,
                            arrow_start_y - direction * arrow_length),
                           (mid_point_x + arrow_tail_width,
                            arrow_start_y)]

            group.add(self.dwg.polyline(points_list,
                                        stroke=section.style.growth_arrow_stroke,
                                        stroke_width=1,
                                        fill=section.style.growth_arrow_fill))

        if section.is_grow_up():
            _make_growth_arrow_generic(section.pos_y, 1)
        if section.is_grow_down():
            _make_growth_arrow_generic(section.pos_y + section.size_y, -1)

        return group

    def _make_main_frame(self, area_view):
        return self.dwg.rect((0, 0), (area_view.size_x, area_view.size_y),
                             fill=area_view.style.background,
                             stroke=area_view.style.stroke,
                             stroke_width=area_view.style.stroke_width)

    def _make_box(self, section: Section):
        return self.dwg.rect((section.pos_x, section.pos_y),
                             (section.size_x, section.size_y),
                             fill=section.style.fill,
                             stroke=section.style.stroke,
                             stroke_width=section.style.stroke_width)

    def _make_break(self, section: Section) -> svgwrite.container.Group:
        """
        Make a break representation for a given section.

        Depending on the selected break type (at style/break_type), break can be wave (~), double
        wave(≈), diagonal(/) or dots(...)
        :param section: Section for which the break wants to be created
        :return: SVG group container with the breaks graphics
        """
        group = self.dwg.g()
        mid_point_x = (section.pos_x + section.size_x) / 2
        mid_point_y = (section.pos_y + section.size_y) / 2
        style = section.style

        def _make_break_dots(_section: Section) -> svgwrite.container.Group:
            """
            Make a break representation using dot style

            :param _section: Section for which the break wants to be created
            :return: SVG group container with the breaks graphics
            """
            rectangle = self.dwg.rect((_section.pos_x, _section.pos_y),
                                      (_section.size_x, _section.size_y))

            rectangle.fill(style.fill)
            rectangle.stroke(style.stroke, width=style.stroke_width)

            group.add(rectangle)

            points_list = [
                (mid_point_x, mid_point_y),
                (mid_point_x, mid_point_y + 12),
                (mid_point_x, mid_point_y - 12),
            ]

            for points_set in points_list:
                group.add(self.dwg.circle(points_set, 3, fill=style.text_fill))

            return group

        def _make_break_wave(_section: Section) -> svgwrite.container.Group:
            """
            Make a break representation using wave style

            :param _section: Section for which the break wants to be created
            :return: SVG group container with the breaks graphics
            """
            wave_len = _section.size_x + 1
            shifts = [(-5, 2/5, 0), (5, 3 / 5, _section.size_y), ]

            for shift in shifts:
                points = [(i, mid_point_y + shift[0] + 2 * cos(i / 24)) for i in range(wave_len)]
                points.extend(
                    [
                        (_section.pos_x + _section.size_x,
                         (_section.pos_y + _section.size_y) * shift[1]),
                        (_section.pos_x + _section.size_x, _section.pos_y + shift[2]),
                        (_section.pos_x, _section.pos_y + shift[2]),
                        (_section.pos_x, mid_point_y + shift[0] + 2 * cos(_section.pos_x / 24)),
                    ]
                )

                group.add(self.dwg.polyline(points,
                                            stroke=style.stroke,
                                            stroke_width=style.stroke_width,
                                            fill=style.fill))

            return group

        def _make_break_double_wave(_section: Section) -> svgwrite.container.Group:
            """
            Make a break representation using double wave style

            :param _section: Section for which the break wants to be created
            :return: SVG group container with the breaks graphics
            """
            points_list = [[
                (_section.pos_x, (_section.pos_y + _section.size_y) * 2 / 5),
                (_section.pos_x, _section.pos_y),
                (_section.pos_x + _section.size_x, _section.pos_y),
                (_section.pos_x + _section.size_x, (_section.pos_y + _section.size_y) * 2 / 5),
            ],
                [
                    (_section.pos_x, (_section.pos_y + _section.size_y) * 3 / 5),
                    (_section.pos_x, _section.pos_y + _section.size_y),
                    (_section.pos_x + _section.size_x, _section.pos_y + _section.size_y),
                    (_section.pos_x + _section.size_x, (_section.pos_y + _section.size_y) * 3 / 5),
                ]
            ]

            rectangle = self.dwg.rect((_section.pos_x, _section.pos_y),
                                      (_section.size_x, _section.size_y))
            rectangle.fill(section.style.fill)
            group.add(rectangle)

            for points_set in points_list:
                group.add(self.dwg.polyline(points_set,
                                            stroke=style.stroke,
                                            stroke_width=style.stroke_width,
                                            fill='none'))
            wave_length = 20
            shifts = [(0, -5),
                      (0, +5),
                      (_section.size_x, -5),
                      (_section.size_x, +5),
                      ]

            for shift in shifts:
                points = [(i - wave_length / 2 + shift[0], mid_point_y + shift[1] + cos(i / 2))
                          for i in range(wave_length)]

                group.add(self.dwg.polyline(points,
                                            stroke=style.stroke,
                                            stroke_width=style.stroke_width,
                                            fill='none'))

            return group

        def _make_break_diagonal(_section: Section) -> svgwrite.container.Group:
            """
            Make a break representation using diagonal style

            :param _section: Section for which the break wants to be created
            :return: SVG group container with the breaks graphics
            """
            points_list = [[(_section.pos_x, _section.pos_y),
                            (_section.pos_x + _section.size_x, _section.pos_y),
                            (_section.pos_x + _section.size_x,
                             (_section.pos_y + _section.size_y) * 3 / 10),
                            (_section.pos_x, (_section.pos_y + _section.size_y) * 5 / 10),
                            (_section.pos_x, _section.pos_y)
                            ], [(_section.pos_x, _section.pos_y + _section.size_y),
                                (_section.pos_x + _section.size_x,
                                 _section.pos_y + _section.size_y),
                                (_section.pos_x + _section.size_x,
                                 (_section.pos_y + _section.size_y) * 5 / 10),
                                (_section.pos_x, (_section.pos_y + _section.size_y) * 7 / 10),
                                (_section.pos_x, _section.pos_y + _section.size_y),
                                ]]

            for points_set in points_list:
                group.add(self.dwg.polyline(points_set,
                                            stroke=style.stroke,
                                            stroke_width=style.stroke_width,
                                            fill=style.fill))

            return group

        breaks = [('/', _make_break_diagonal),
                  ('≈', _make_break_double_wave),
                  ('~', _make_break_wave),
                  ('...', _make_break_dots), ]

        for _break in breaks:
            if style.break_type == _break[0]:
                return _break[1](section)

    def _make_text(self,
                   text,
                   position,
                   style,
                   text_type='normal',
                   **kwargs):

        if text_type == 'title':
            size = '24px'
        elif text_type == 'small':
            size = '12px'
        else:
            size = style.font_size

        return self.dwg.text(text, insert=(position[0], position[1]),
                             stroke=style.text_stroke,
                             # focusable='true',
                             fill=style.text_fill,
                             stroke_width=style.text_stroke_width,
                             font_size=size,
                             font_weight="normal",
                             font_family=style.font_type,
                             text_anchor=kwargs.get('anchor', 'middle'),
                             alignment_baseline=kwargs.get('baseline', 'middle')
                             )

    def _make_name(self, section):
        name = section.name if section.name is not None else section.id
        return self._make_text(name,
                               (section.name_label_pos_x, section.name_label_pos_y),
                               style=section.style,
                               anchor='middle',
                               )

    def _make_size_label(self, section):
        return self._make_text(hex(section.size),
                               (section.size_label_pos[0], section.size_label_pos[1]),
                               section.style,
                               anchor='start',
                               baseline='hanging',
                               text_type='small'
                               )

    def _make_address(self, section):
        return self._make_text(hex(section.address),
                               (section.addr_label_pos_x, section.addr_label_pos_y),
                               anchor='start',
                               style=section.style)

    def _make_section(self, group, section: Section, area_view):
        section.size_x = area_view.size_x
        section.size_y = area_view.to_pixels(section.size)
        section.pos_y = area_view.to_pixels(area_view.end_address - section.size - section.address)
        section.pos_x = 0

        if section.is_break():
            group.add(self._make_break(section))
        else:
            group.add(self._make_box(section))
            if not section.is_name_hidden():
                group.add(self._make_name(section))
            if not section.is_address_hidden():
                group.add(self._make_address(section))
            if not section.is_size_hidden():
                group.add(self._make_size_label(section))

        return group

    def _get_points_for_address(self, address, area_view):
        left_block_view = self.area_views[0]
        right_block_view = area_view

        left_block_x = left_block_view.size_x + left_block_view.pos_x
        left_block_x2 = left_block_x + 30
        left_block_y = left_block_view.pos_y + left_block_view.to_pixels_relative(address)

        right_block_x = area_view.pos_x
        right_block_x2 = right_block_x - 30
        right_block_y = right_block_view.pos_y + right_block_view.to_pixels_relative(address)

        return [(left_block_x, left_block_y),
                (left_block_x2, left_block_y),
                (right_block_x2, right_block_y),
                (right_block_x, right_block_y),
                ]

    def _make_poly(self, area_view, start_address, end_address, style):

        def find_right_subarea_view(address, area):
            """
            Given an area, find the subarea where the provided address is

            :param address: Address to look for
            :param area: Area that contains the subarea to be found
            :return: Found subarea, if not found, parent area
            """
            for subarea in area.get_split_area_views():
                if subarea.start_address <= address <= subarea.end_address:
                    return subarea
            return area

        points = []

        end_subarea = find_right_subarea_view(end_address, area_view)
        start_subarea = find_right_subarea_view(start_address, area_view)

        _reversed = self._get_points_for_address(end_address, end_subarea)
        _reversed.reverse()
        points.extend(self._get_points_for_address(start_address, start_subarea))
        points.extend(_reversed)

        return self.dwg.polyline(points,
                                 stroke=style.stroke,
                                 stroke_width=style.stroke_width,
                                 fill=style.fill,
                                 opacity=style.opacity)

    def _make_arrow_head(self, label, direction='down'):
        if direction == 'left':
            angle = 90
        elif direction == 'right':
            angle = 270
        elif direction == 'up':
            angle = 0
        else:
            angle = 180

        arrow_head_width = 5 * label.style.weight
        arrow_head_height = 10 * label.style.weight
        group = self.dwg.g()
        points_list = [(0, 0 - arrow_head_height),
                       (0 - arrow_head_width, 0 - arrow_head_height),
                       (0, 0),
                       (0 + arrow_head_width, 0 - arrow_head_height),
                       (0, 0 - arrow_head_height),
                       ]

        poly = self.dwg.polyline(points_list,
                                 stroke=label.style.stroke,
                                 stroke_width=1,
                                 fill=label.style.stroke)
        poly.rotate(angle, center=(0, 0))
        group.add(poly)

        return group

    def _make_label(self, label, area_view):
        line_label_spacer = 3
        g = self.dwg.g()
        address = label.address
        text = label.text
        label_length = label.length

        if address is None:
            raise KeyError("A label without address was found")

        if label.side == Side.RIGHT:
            pos_x_d = area_view.size_x
            direction = 1
            anchor = 'start'

        else:
            pos_x_d = 0
            direction = -1
            anchor = 'end'

        pos_y = area_view.to_pixels_relative(address)
        points = [(0 + pos_x_d, pos_y), (direction*(label_length + pos_x_d), pos_y)]

        def add_arrow_head(_direction):
            arrow_direction = 'right'
            if 'in' == _direction:
                if label.side == Side.LEFT:
                    arrow_direction = 'right'
                elif label.side == Side.RIGHT:
                    arrow_direction = 'left'

                arrow_head_x = pos_x_d

            elif 'out' == _direction:
                if label.side == Side.LEFT:
                    arrow_direction = 'left'
                elif label.side == Side.RIGHT:
                    arrow_direction = 'right'

                arrow_head_x = direction * (label_length + pos_x_d)

            else:
                logger.warning(f"Invalid direction {_direction} provided")
                return

            g.add(self._make_arrow_head(label, direction=arrow_direction))\
                .translate(arrow_head_x, pos_y)

        if type(label.directions) == str:
            add_arrow_head(label.directions)
        elif type(label.directions) == list:
            for head_direction in label.directions:
                add_arrow_head(head_direction)

        g.add(self._make_text(text,
                              (direction*(pos_x_d + label_length + line_label_spacer), pos_y),
                              label.style,
                              anchor=anchor))

        g.add(self.dwg.polyline(points,
                                stroke=label.style.stroke,
                                stroke_dasharray=label.style.stroke_dasharray,
                                stroke_width=label.style.stroke_width
                                ))
        return g

    def _make_link(self, address, style):
        hlines = self.dwg.g(id='hlines', stroke='grey')

        for area_view in self.area_views[1:]:
            for subarea in area_view.get_split_area_views():

                if not subarea.sections.has_address(address):
                    continue

                def _make_line(x1, y1, x2, y2):
                    return self.dwg.line(start=(x1, y1), end=(x2, y2),
                                         stroke_width=style.stroke_width,
                                         stroke=style.stroke)

                points = self._get_points_for_address(address, subarea)

                hlines.add(_make_line(x1=points[0][0], y1=points[0][1],
                                      x2=points[1][0], y2=points[1][1]))

                hlines.add(_make_line(x1=points[1][0], y1=points[1][1],
                                      x2=points[2][0], y2=points[2][1]))

                hlines.add(_make_line(x1=points[2][0], y1=points[2][1],
                                      x2=points[3][0], y2=points[3][1]))
        return hlines


================================================
FILE: requirements.txt
================================================
svgwrite==1.4.3
pyyaml==6.0.1


================================================
FILE: section.py
================================================
from style import Style


class Section:
    """
    Holds logical and graphical information for a given section, as well as other properties such as
    style, visibility, type, etc...
    """
    size: int
    address: int
    id: str
    size_x: int
    size_y: int
    pos_x: int
    pos_y: int
    label_offset: int = 10
    style: Style

    def __init__(self, size, address, id, _type, parent, flags=[], name=None):
        self.type = _type
        self.parent = parent
        self.size = size
        self.address = address
        self.id = id
        self.name = name
        self.size_y = 0
        self.size_x = 0
        self.style = Style()
        self.flags = flags

    def is_grow_up(self):
        return 'grows-up' in self.flags

    def is_grow_down(self):
        return 'grows-down' in self.flags

    def is_break(self):
        return 'break' in self.flags

    def is_hidden(self):
        return 'hidden' in self.flags

    def _should_element_be_hidden(self, attribute):
        return True if str(attribute) in ['True', 'yes'] \
            else False if str(attribute) in ['False', 'no'] \
            else self.size_y < 20

    def is_address_hidden(self):
        return self._should_element_be_hidden(self.style.hide_address)

    def is_name_hidden(self):
        return self._should_element_be_hidden(self.style.hide_name)

    def is_size_hidden(self):
        return self._should_element_be_hidden(self.style.hide_size)

    @property
    def addr_label_pos_x(self):
        return self.size_x + self.label_offset

    @property
    def addr_label_pos_y(self):
        return self.pos_y + self.size_y

    @property
    def name_label_pos_x(self):
        return self.size_x / 2

    @property
    def size_label_pos(self):
        return self.pos_x + 2, self.pos_y + 2

    @property
    def name_label_pos_y(self):
        return self.pos_y + (self.size_y / 2)


================================================
FILE: sections.py
================================================
from section import Section


class Sections:
    """
    Provide methods and to select and filter sections according to their base address, size, parent,
    type,...
    """
    sections: [Section] = []

    def __init__(self, sections: [Section]):
        self.sections = sections

    def get_sections(self) -> [Section]:
        return self.sections

    @property
    def highest_section(self) -> int:
        return max(self.sections, key=lambda x: x.address)

    @property
    def highest_address(self) -> int:
        return max(self.sections, key=lambda x: x.address).address

    @property
    def highest_memory(self) -> int:
        section = max(self.sections, key=lambda x: x.address + x.size)
        return section.address + section.size

    @property
    def lowest_memory(self) -> int:
        return min(self.sections, key=lambda x: x.address).address

    @property
    def lowest_size(self) -> int:
        return min(self.sections, key=lambda x: x.size).size

    def has_address(self, address: int) -> bool:
        for section in self.sections:
            if section.address <= address <= (section.address + section.size):
                return True
        return False

    def is_break_section_group(self):
        for section in self.get_sections():
            if section.is_break():
                return True
        return False

    def filter_size_min(self, size_bytes: int):
        return Sections(self.sections) if size_bytes is None \
            else Sections(list(filter(lambda item: item.size > size_bytes, self.sections)))

    def filter_size_max(self, size_bytes: int):
        return Sections(self.sections) if size_bytes is None \
            else Sections(list(filter(lambda item: item.size < size_bytes, self.sections)))

    def filter_address_max(self, address_bytes: int):
        return Sections(self.sections) if address_bytes is None \
            else Sections(list(filter(lambda item: (item.address + item.size)
                                                   <= address_bytes, self.sections)))

    def filter_address_min(self, address_bytes: int):
        return Sections(self.sections) if address_bytes is None \
            else Sections(list(filter(lambda item: item.address >= address_bytes, self.sections)))

    def filter_type(self, _type: str):
        return Sections(self.sections) if _type is None \
            else Sections(list(filter(lambda item: item.filter_type == _type, self.sections)))

    def filter_parent(self, parent: str):
        return Sections(self.sections) if parent is None \
            else Sections(list(filter(lambda item: item.filter_parent == parent, self.sections)))

    def filter_breaks(self):
        return Sections(list(filter(lambda item: item.is_break(), self.sections)))

    def split_sections_around_breaks(self) -> []:
        """
        Split a Sections object into different Sections objects having a break section as delimiter

        :return: A list of Section objects
        """
        split_sections = []
        previous_break_end_address = self.lowest_memory

        breaks = self.filter_breaks().get_sections()

        for _break in breaks:

            # Section that covers from previous break till start of this break
            # If it was the first break, will cover from begining of the whole area to this break.
            # Only append if search returns more than 0 counts
            s = Sections(sections=self.sections) \
                .filter_address_max(_break.address) \
                .filter_address_min(previous_break_end_address)
            if len(s.get_sections()) > 0:
                split_sections.append(s)

            # This section covers the break itself
            split_sections.append(Sections(sections=[_break]))
            previous_break_end_address = _break.address + _break.size

        # Section that covers from the last break end address to the end of the whole area. Only
        # append if search returns more than 0 counts
        last_group = Sections(sections=self.sections) \
            .filter_address_max(self.highest_memory) \
            .filter_address_min(previous_break_end_address)

        if len(last_group.sections) > 0:
            split_sections.append(last_group)

        return split_sections


================================================
FILE: style.py
================================================
class Style:
    """
    Holds style for different rendering objects
    """
    # Non SVG
    background: str
    break_type: str
    break_size: int
    growth_arrow_size: float
    growth_arrow_stroke: str
    growth_arrow_fill: str
    stroke_dasharray: str
    hide_size: str
    hide_name: str
    hide_address: str

    # SVG
    fill: str
    stroke: str
    stroke_width: int
    size: int
    font_size: int
    font_type: str
    weight: int
    opacity: int

    text_stroke: str
    text_stroke_width: int
    text_fill: str

    weight: int

    def __init__(self, style=None):
        if style is not None:
            for key, value in style.items():
                setattr(self, key.replace('-', '_'), style.get(key, value))

    def override_properties_from(self, style):
        """
        Modify self by adding additional members available at the provided style

        :param style: Style whose members wants to be added
        :return: New merged styl
        """
        members = [attr for attr in dir(style) if
                   not callable(getattr(style, attr)) and not attr.startswith("__") and getattr(
                       style, attr) is not None]

        for member in members:
            value = getattr(style, member)
            setattr(self, member, value)

        return self

    @staticmethod
    def get_default():
        """
        Get an initialized default Style instance
        :return: A default initialized Style instance
        """

        default_style = Style()
        default_style.break_type = '≈'
        default_style.break_size = 20

        default_style.growth_arrow_size = 1

        default_style.background = 'white'
        default_style.stroke = 'black'
        default_style.stroke_width = 1
        default_style.size = 2

        default_style.font_size = 16
        default_style.font_type = 'Helvetica'

        default_style.weight = 1
        default_style.opacity = 1

        default_style.text_stroke = 'black'
        default_style.text_fill = 'black'
        default_style.text_stroke_width = 0

        default_style.fill = 'lightgrey'
        default_style.growth_arrow_fill = 'white'
        default_style.growth_arrow_stroke = 'black'
        default_style.stroke_dasharray = '3,2'
        default_style.weight = 2
        default_style.hide_size = 'auto'
        default_style.hide_name = 'auto'
        default_style.hide_address = 'auto'

        return default_style
Download .txt
gitextract_tz97aatf/

├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── main.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── area_view.py
├── examples/
│   ├── break_config.yaml
│   ├── break_map.yaml
│   ├── labels_config.yaml
│   ├── labels_map.yaml
│   ├── link_config.yaml
│   ├── link_map.yaml
│   ├── sample_config.yaml
│   ├── stack_config.yaml
│   ├── stack_map.yaml
│   ├── stm32f103_config.yaml
│   └── stm32f103_map.yaml
├── gnu_linker_map_parser.py
├── helpers.py
├── labels.py
├── linkerscope.py
├── links.py
├── logger.py
├── map_file_loader.py
├── map_render.py
├── requirements.txt
├── section.py
├── sections.py
└── style.py
Download .txt
SYMBOL INDEX (91 symbols across 12 files)

FILE: area_view.py
  class AreaView (line 9) | class AreaView:
    method __init__ (line 22) | def __init__(self,
    method get_split_area_views (line 54) | def get_split_area_views(self):
    method to_pixels (line 61) | def to_pixels(self, value) -> float:
    method to_pixels_relative (line 71) | def to_pixels_relative(self, value) -> float:
    method _overwrite_sections_info (line 85) | def _overwrite_sections_info(self):
    method _process (line 125) | def _process(self):
    method _get_break_total_size_before_transform_px (line 231) | def _get_break_total_size_before_transform_px(self):
    method _get_non_breaks_total_size_px (line 244) | def _get_non_breaks_total_size_px(self, breaks_size_y_sum_px):

FILE: gnu_linker_map_parser.py
  class GNULinkerMapParser (line 7) | class GNULinkerMapParser:
    method __init__ (line 11) | def __init__(self, input_filename, output_filename):
    method parse (line 17) | def parse(self):
    method process_areas (line 52) | def process_areas(self, line):
    method process_sections (line 67) | def process_sections(self, line):

FILE: helpers.py
  class DefaultAppValues (line 4) | class DefaultAppValues:
  function safe_element_list_get (line 12) | def safe_element_list_get(_list: [], index: int, default=None) -> int:
  function safe_element_dict_get (line 25) | def safe_element_dict_get(_dict: {}, key: str, default=None) -> int:

FILE: labels.py
  class Labels (line 7) | class Labels:
    method __init__ (line 15) | def __init__(self, labels, style):
    method build_labels (line 19) | def build_labels(self, labels_yaml) -> []:
  class Side (line 40) | class Side:
  class Label (line 46) | class Label:
    method __init__ (line 51) | def __init__(self, style):

FILE: linkerscope.py
  function parse_arguments (line 18) | def parse_arguments():
  function get_area_views (line 42) | def get_area_views(_raw_sections, _base_style, config=None):

FILE: links.py
  class Links (line 8) | class Links:
    method __init__ (line 17) | def __init__(self, links=None, style=None):
    method configuration_validator (line 24) | def configuration_validator(self):

FILE: logger.py
  class CustomFormatter (line 5) | class CustomFormatter(logging.Formatter):
    method format (line 22) | def format(self, record):

FILE: map_file_loader.py
  class MapFileLoader (line 9) | class MapFileLoader:
    method __init__ (line 15) | def __init__(self, file, convert):
    method parse (line 19) | def parse(self):
    method parse_yaml (line 39) | def parse_yaml(filename):
    method parse_map (line 59) | def parse_map(input_filename):

FILE: map_render.py
  class MapRender (line 12) | class MapRender:
    method __init__ (line 22) | def __init__(self, area_view, links, file='map.svg', size=DefaultAppVa...
    method _get_valid_linked_sections (line 36) | def _get_valid_linked_sections(self, linked_sections):
    method draw (line 105) | def draw(self):
    method _make_title (line 221) | def _make_title(self, area_view):
    method _make_growth (line 231) | def _make_growth(self, section: Section) -> svgwrite.container.Group:
    method _make_main_frame (line 273) | def _make_main_frame(self, area_view):
    method _make_box (line 279) | def _make_box(self, section: Section):
    method _make_break (line 286) | def _make_break(self, section: Section) -> svgwrite.container.Group:
    method _make_text (line 443) | def _make_text(self,
    method _make_name (line 469) | def _make_name(self, section):
    method _make_size_label (line 477) | def _make_size_label(self, section):
    method _make_address (line 486) | def _make_address(self, section):
    method _make_section (line 492) | def _make_section(self, group, section: Section, area_view):
    method _get_points_for_address (line 511) | def _get_points_for_address(self, address, area_view):
    method _make_poly (line 529) | def _make_poly(self, area_view, start_address, end_address, style):
    method _make_arrow_head (line 560) | def _make_arrow_head(self, label, direction='down'):
    method _make_label (line 589) | def _make_label(self, label, area_view):
    method _make_link (line 655) | def _make_link(self, address, style):

FILE: section.py
  class Section (line 4) | class Section:
    method __init__ (line 19) | def __init__(self, size, address, id, _type, parent, flags=[], name=No...
    method is_grow_up (line 31) | def is_grow_up(self):
    method is_grow_down (line 34) | def is_grow_down(self):
    method is_break (line 37) | def is_break(self):
    method is_hidden (line 40) | def is_hidden(self):
    method _should_element_be_hidden (line 43) | def _should_element_be_hidden(self, attribute):
    method is_address_hidden (line 48) | def is_address_hidden(self):
    method is_name_hidden (line 51) | def is_name_hidden(self):
    method is_size_hidden (line 54) | def is_size_hidden(self):
    method addr_label_pos_x (line 58) | def addr_label_pos_x(self):
    method addr_label_pos_y (line 62) | def addr_label_pos_y(self):
    method name_label_pos_x (line 66) | def name_label_pos_x(self):
    method size_label_pos (line 70) | def size_label_pos(self):
    method name_label_pos_y (line 74) | def name_label_pos_y(self):

FILE: sections.py
  class Sections (line 4) | class Sections:
    method __init__ (line 11) | def __init__(self, sections: [Section]):
    method get_sections (line 14) | def get_sections(self) -> [Section]:
    method highest_section (line 18) | def highest_section(self) -> int:
    method highest_address (line 22) | def highest_address(self) -> int:
    method highest_memory (line 26) | def highest_memory(self) -> int:
    method lowest_memory (line 31) | def lowest_memory(self) -> int:
    method lowest_size (line 35) | def lowest_size(self) -> int:
    method has_address (line 38) | def has_address(self, address: int) -> bool:
    method is_break_section_group (line 44) | def is_break_section_group(self):
    method filter_size_min (line 50) | def filter_size_min(self, size_bytes: int):
    method filter_size_max (line 54) | def filter_size_max(self, size_bytes: int):
    method filter_address_max (line 58) | def filter_address_max(self, address_bytes: int):
    method filter_address_min (line 63) | def filter_address_min(self, address_bytes: int):
    method filter_type (line 67) | def filter_type(self, _type: str):
    method filter_parent (line 71) | def filter_parent(self, parent: str):
    method filter_breaks (line 75) | def filter_breaks(self):
    method split_sections_around_breaks (line 78) | def split_sections_around_breaks(self) -> []:

FILE: style.py
  class Style (line 1) | class Style:
    method __init__ (line 33) | def __init__(self, style=None):
    method override_properties_from (line 38) | def override_properties_from(self, style):
    method get_default (line 56) | def get_default():
Condensed preview — 30 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (112K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 892,
    "preview": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [u"
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 596,
    "preview": "name: Pylint\n\non: [push]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: "
  },
  {
    "path": ".gitignore",
    "chars": 3126,
    "preview": "# 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_St"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 2735,
    "preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changel"
  },
  {
    "path": "LICENSE",
    "chars": 1067,
    "preview": "MIT License\n\nCopyright (c) 2023 Raúl Gotor\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "README.md",
    "chars": 16827,
    "preview": "# LinkerScope\n\n## Project summary\n\nLinkerScope is a memory map diagram generator. It can be fed either with a GNU Linker"
  },
  {
    "path": "area_view.py",
    "chars": 11401,
    "preview": "import copy\n\nfrom helpers import safe_element_list_get, safe_element_dict_get, DefaultAppValues\nfrom labels import Label"
  },
  {
    "path": "examples/break_config.yaml",
    "chars": 1140,
    "preview": "size: [950, 570]\n\nvariables:\n  graphite: &graphite '#2A363B'\n  pastel_green: &pastel_green '#99B898'\n\nstyle:\n  text-fill"
  },
  {
    "path": "examples/break_map.yaml",
    "chars": 306,
    "preview": "map:\n- address: 0x20000000\n  size:    0x20000000\n  id: SRAM Area\n  type: area\n- address: 0x40000000\n  size:    0x2000000"
  },
  {
    "path": "examples/labels_config.yaml",
    "chars": 1506,
    "preview": "size: [800, 300]\nvariables:\n  graphite: &graphite '#2A363B'\n  pastel_green: &pastel_green '#99B898'\n  pastel_yellow: &pa"
  },
  {
    "path": "examples/labels_map.yaml",
    "chars": 300,
    "preview": "map:\n- address: 0xE8040000\n  size:    0x00001000\n  id: TPIU\n  type: section\n- address: 0xE8041000\n  size:    0x00001000\n"
  },
  {
    "path": "examples/link_config.yaml",
    "chars": 1829,
    "preview": "size: [750, 650]\nvariables:\n  graphite: &graphite '#2A363B'\n  pastel_green: &pastel_green '#99B898'\n  pastel_yellow: &pa"
  },
  {
    "path": "examples/link_map.yaml",
    "chars": 1766,
    "preview": "map:\n- address: 0x20000000\n  size:    0x20000000\n  id: SRAM Area\n  type: area\n- address: 0x40000000\n  size:    0x2000000"
  },
  {
    "path": "examples/sample_config.yaml",
    "chars": 519,
    "preview": "size: [500,1100]\n\nvariables:\n  graphite: &graphite '#212b38'\n  cleargraphite: &cleargraphite '#37465b'\n  mydarkgreen: &m"
  },
  {
    "path": "examples/stack_config.yaml",
    "chars": 802,
    "preview": "size: [300, 600]\n\nvariables:\n  green:  &green '#47b39d'\n  yellow: &yellow '#ffc153'\n  orange: &orange '#eb6b56'\n  granat"
  },
  {
    "path": "examples/stack_map.yaml",
    "chars": 413,
    "preview": "map:\n- address: 0x20000000\n  size:    0x10000000\n  id: Text\n  type: area\n- address: 0x30000000\n  size:    0x10000000\n  i"
  },
  {
    "path": "examples/stm32f103_config.yaml",
    "chars": 2194,
    "preview": "size: [1100,1100]\n\nvariables:\n  graphite: &graphite '#212b38'\n  cleargraphite: &cleargraphite '#37465b'\n  myturqoise: &m"
  },
  {
    "path": "examples/stm32f103_map.yaml",
    "chars": 5228,
    "preview": "map:\n  - id: Aliased\n    address: 0x00000000\n    size:    0x08000000\n    type: area\n\n  - id: Flash Memory\n    address: 0"
  },
  {
    "path": "gnu_linker_map_parser.py",
    "chars": 2895,
    "preview": "import re\nimport yaml\n\nfrom section import Section\n\n\nclass GNULinkerMapParser:\n    \"\"\"\n    Parse a GNU linker map file a"
  },
  {
    "path": "helpers.py",
    "chars": 1158,
    "preview": "from logger import logger\n\n\nclass DefaultAppValues:\n    DOCUMENT_SIZE = (400, 700)\n    POSITION_X = 50\n    POSITION_Y = "
  },
  {
    "path": "labels.py",
    "chars": 1429,
    "preview": "import copy\nfrom dataclasses import dataclass\nfrom style import Style\n\n\n@dataclass\nclass Labels:\n    \"\"\"\n    Container f"
  },
  {
    "path": "linkerscope.py",
    "chars": 6118,
    "preview": "#!/usr/bin/env python3\n\nimport argparse\nimport copy\n\nimport yaml\n\nfrom area_view import AreaView\nfrom helpers import saf"
  },
  {
    "path": "links.py",
    "chars": 2365,
    "preview": "import logging\n\nfrom helpers import safe_element_dict_get\nfrom style import Style\nfrom logger import CustomFormatter, lo"
  },
  {
    "path": "logger.py",
    "chars": 993,
    "preview": "import logging\n\n\n# Adaptation from https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output\nclas"
  },
  {
    "path": "map_file_loader.py",
    "chars": 2085,
    "preview": "import os\nimport sys\nimport yaml\nfrom logger import logger\nfrom section import Section\nfrom gnu_linker_map_parser import"
  },
  {
    "path": "map_render.py",
    "chars": 28723,
    "preview": "from math import cos\nfrom svgwrite import Drawing\nimport svgwrite\n\nfrom helpers import DefaultAppValues\nfrom labels impo"
  },
  {
    "path": "requirements.txt",
    "chars": 30,
    "preview": "svgwrite==1.4.3\npyyaml==6.0.1\n"
  },
  {
    "path": "section.py",
    "chars": 1902,
    "preview": "from style import Style\n\n\nclass Section:\n    \"\"\"\n    Holds logical and graphical information for a given section, as wel"
  },
  {
    "path": "sections.py",
    "chars": 4295,
    "preview": "from section import Section\n\n\nclass Sections:\n    \"\"\"\n    Provide methods and to select and filter sections according to"
  },
  {
    "path": "style.py",
    "chars": 2464,
    "preview": "class Style:\n    \"\"\"\n    Holds style for different rendering objects\n    \"\"\"\n    # Non SVG\n    background: str\n    break"
  }
]

About this extraction

This page contains the full source code of the raulgotor/linkerscope GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 30 files (104.6 KB), approximately 25.9k tokens, and a symbol index with 91 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!