Skip to content

Commit

Permalink
Merge pull request #15 from CESNET/cabal_improve
Browse files Browse the repository at this point in the history
SPHINX VHDL version 0.2.0
  • Loading branch information
jakubcabal authored Jul 23, 2024
2 parents ad75764 + bf4bee5 commit 0e116df
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 90 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- name: Setup and build 🔧
run: |
sudo apt-get install python3-pip
pip3 install sphinx sphinx-vhdl
pip3 install sphinx sphinx-vhdl sphinx_rtd_theme
sphinx-build -M html doc/source public
touch public/html/.nojekyll
- name: Deploy 🚀
Expand Down
21 changes: 0 additions & 21 deletions .gitlab-ci.yml

This file was deleted.

16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Sphinx-vhdl
# SPHINX-VHDL

[![PyPI](https://img.shields.io/pypi/v/sphinx-vhdl)](https://pypi.org/project/sphinx-vhdl/)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/CESNET/sphinx-vhdl/documentation?label=documentation)](https://cesnet.github.io/sphinx-vhdl/)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/CESNET/sphinx-vhdl/.github/workflows/doc.yml)](https://cesnet.github.io/sphinx-vhdl/)

> A [sphinx](https://www.sphinx-doc.org/) domain for semi-automatically documenting VHDL
This extension for Sphinx allows you to keep your documentation in code and automatically draw it out into your main documentation using just few simple directives.

You can see the detailed documentation at https://cesnet.github.io/sphinx-vhdl/, or build it yourself (running `make html` while in the `doc` directory and having `sphinx` installed should be sufficient)
You can see the detailed documentation at https://cesnet.github.io/sphinx-vhdl/, or build it yourself (running `make` while in the `doc` directory and having `sphinx` + `sphinx_rtd_theme` installed should be sufficient)

## Usage

Expand All @@ -16,7 +16,9 @@ The python package must be installed with
pip3 install sphinx-vhdl
```

The usage of this extension requires Python >= 3.6 and Sphinx >= 4.0.0.
This extension requires Python >= 3.8 and Sphinx >= 6.0.0.

*Note that your documentation may use multiple sphinx extensions or an alternative theme (such as `sphinx_rtd_theme`), which you must also have installed.*

## Configuration

Expand All @@ -27,6 +29,12 @@ extensions = ['sphinxvhdl.vhdl']
vhdl_autodoc_source_path = 'path/to/your/vhdl/sources/root'
```

## Where is the SPHINX-VHDL extension used?

- [Open FPGA Modules (OFM) by CESNET](https://github.com/CESNET/ofm/)
- [NDK Minimal Application by CESNET](https://github.com/CESNET/ndk-app-minimal)
- *Do you use SPHINX-VHDL in your public VHDL repository? Please add a link to this list!*

## Repository maintainer

- Jakub Cabal, [email protected]
Expand Down
9 changes: 9 additions & 0 deletions doc/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Minimal makefile for Sphinx documentation

.PHONY: all clean

all:
sphinx-build -M html source build -v -E

clean:
rm -rf ./build
6 changes: 3 additions & 3 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# -- Project information -----------------------------------------------------

project = 'sphinx-vhdl'
copyright = '2021, Cesnet z.s.p.o.'
copyright = '2024, Cesnet z.s.p.o.'
author = 'Cesnet z.s.p.o.'


Expand All @@ -31,7 +31,7 @@
'sphinxvhdl.vhdl'
]

vhdl_autodoc_source_path = 'doc/source'
vhdl_autodoc_source_path = './'

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
Expand All @@ -47,7 +47,7 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
html_theme = 'sphinx_rtd_theme'

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
Expand Down
2 changes: 1 addition & 1 deletion doc/source/example_built.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Example Documentation

.. vhdl:autopackage:: math_pack
.. vhdl:autofunction:: log2
.. vhdl:autofunction:: log2
.. vhdl:autoentity:: counter
.. vhdl:autoconstants:: counter
6 changes: 4 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = sphinx-vhdl
version = 0.1.6
version = 0.2.0
author = CESNET z.s.p.o.
author_email = [email protected]
description = A Sphinx domain and autodocumenter for VHDL
Expand All @@ -21,7 +21,9 @@ classifiers =
package_dir =
= src
packages = find:
python_requires = >=3.6
python_requires = >=3.8
install_requires =
sphinx >= 6.0

[options.packages.find]
where = src
63 changes: 27 additions & 36 deletions src/sphinxvhdl/autodoc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# autodoc.py: A basic VHDL parser and documentation extractor
# Copyright (C) 2021 CESNET z.s.p.o.
# Author(s): Jindrich Dite <[email protected]>
# Jakub Cabal <[email protected]>
#
# SPDX-License-Identifier: BSD-3-Clause

Expand All @@ -10,9 +11,8 @@
from typing import Optional, List
from enum import Enum, auto

import logging

LOG = logging.getLogger('sphinxvhdl-autodoc')
from sphinx.util import logging
logger = logging.getLogger(__name__)

entities = {}
portsignals = defaultdict(dict)
Expand All @@ -28,24 +28,14 @@
functions = {}

# Function for parsing line comments
def parse_inline_doc_or_raise(line: str, current_doc: List[str]):
def parse_inline_doc_or_print_error(current_doc, filename, line, lineno):
if '-- ' in line:
if len(current_doc) > 0:
raise ValueError(
'Documented entity has both a pre- and inline documentation; only one is allowed. Offending line:\n' +
line)
logger.warning(f"SPHINX-VHDL: Documented entity has both a pre- and inline documentation; only one is allowed!\n Offending line: {line}", location=f"{filename}:{lineno}")
else:
current_doc.append(line.split('-- ', 1)[1])


def parse_inline_doc_or_print_error(current_doc, filename, line, lineno):
try:
parse_inline_doc_or_raise(line, current_doc)
except ValueError as ex:
LOG.warning(f'Error parsing file {filename} at line {lineno}:')
LOG.warning(ex.args)


class ParseState(Enum):
ENTITY_DECL = auto()
ARCH_DECL = auto()
Expand Down Expand Up @@ -79,24 +69,25 @@ def init(path: str) -> None:
for line in source_code:
lineno += 1
line = line.strip()
line_lowercase = line.lower()
# Group parsing logic
if state == ParseState.PORT and group_state == ParseState.GENERIC:
current_group = ""

# Line comments logic
if line.startswith('-- '):
if line_lowercase.startswith('-- '):
# Logic for sampling names of groups of ports and generics
if (state == ParseState.PORT or state == ParseState.GENERIC) and '====' in line:
if (state == ParseState.PORT or state == ParseState.GENERIC) and '====' in line_lowercase:
group_state = state
state = ParseState.GROUPS
current_group = ""
current_doc = []
elif state == ParseState.GROUPS and current_group != '' and '====' not in line:
elif state == ParseState.GROUPS and current_group != '' and '====' not in line_lowercase:
current_doc.append(line[3:])
elif state == ParseState.GROUPS and '====' not in line:
elif state == ParseState.GROUPS and '====' not in line_lowercase:
current_group = current_entity + " " + line[3:].strip()
current_doc = []
elif state == ParseState.GROUPS and '====' in line:
elif state == ParseState.GROUPS and '====' in line_lowercase:
group_definition = current_doc
groups_desc[current_group] = group_definition
state = group_state
Expand All @@ -105,12 +96,12 @@ def init(path: str) -> None:
current_doc.append(line[3:])

# If line start with keyword architecture then save name of architecture
elif line.lower().startswith('architecture'):
elif line_lowercase.startswith('architecture'):
state = ParseState.ARCH_DECL
current_constant = line.split()[3]

# If line contains keyword constant and state is not generice then start to collecting constants
elif state == ParseState.ARCH_DECL and 'constant' in line:
elif state == ParseState.ARCH_DECL and 'constant' in line_lowercase:
parse_inline_doc_or_print_error(current_doc, filename, line, lineno)
definition = line.split('--')[0].split(';')[0]
if ':=' not in definition:
Expand All @@ -120,30 +111,30 @@ def init(path: str) -> None:
current_doc = []

# If there is -- without gap, then ignore
elif line == '--':
elif line_lowercase == '--':
current_doc.append('')

# If there is word entity then try parse, save entity name and add description of entity to associative array
# ID of ass. array is name of entity. At the end clear current description and change state to entity declaration
elif line.lower().startswith('entity ') and ' is' in line:
elif line_lowercase.startswith('entity ') and ' is' in line_lowercase:
parse_inline_doc_or_print_error(current_doc, filename, line, lineno)
current_entity = line.split()[1]
entities[current_entity.lower()] = current_doc
current_doc = []
state = ParseState.ENTITY_DECL

# Check if there is any port declaration
elif state == ParseState.ENTITY_DECL and line.lower().startswith('port'):
elif state == ParseState.ENTITY_DECL and line_lowercase.startswith('port'):
state = ParseState.PORT
current_doc = []

# Check if there is any generic declaration
elif state == ParseState.ENTITY_DECL and line.lower().startswith('generic'):
elif state == ParseState.ENTITY_DECL and line_lowercase.startswith('generic'):
state = ParseState.GENERIC
current_doc = []

# If there is line which contains ":" then it's one of ports, parse it and save his definition
elif state == ParseState.PORT and ':' in line:
elif state == ParseState.PORT and ':' in line_lowercase:
parse_inline_doc_or_print_error(current_doc, filename, line, lineno)
definition = line.split('--')[0].split(';')[0].split(':=')[0].strip()
if definition.lower().startswith('signal'):
Expand All @@ -157,7 +148,7 @@ def init(path: str) -> None:
current_doc = []

# If there is line which contains ":" then it's one of generic, parse it and save his definition
elif state == ParseState.GENERIC and ':' in line:
elif state == ParseState.GENERIC and ':' in line_lowercase:
parse_inline_doc_or_print_error(current_doc, filename, line, lineno)
definition = line.split('--')[0].split(';')[0].strip()
if ':=' not in definition:
Expand All @@ -173,27 +164,27 @@ def init(path: str) -> None:
current_doc = []

# End of the entity was found
elif state == ParseState.ENTITY_DECL and line.lower().startswith('end'):
elif state == ParseState.ENTITY_DECL and line_lowercase.startswith('end'):
state = None
group_state = None
current_doc = []

# If there is magic word package then parse package and save his definition
elif (state is None or state is ParseState.PACKAGE) and line.lower().startswith('package'):
elif (state is None or state is ParseState.PACKAGE) and line_lowercase.startswith('package'):
parse_inline_doc_or_print_error(current_doc, filename, line, lineno)
state = ParseState.PACKAGE
current_package = ('' if current_package == '' else (current_package + '.')) + line.split()[1]
packages[current_package.lower()] = current_doc
current_doc = []

# Signalization of end of the package
elif state is ParseState.PACKAGE and line.lower().startswith('end package'):
elif state is ParseState.PACKAGE and line_lowercase.startswith('end package'):
current_package = '.'.join(current_package.split('.')[:-1])
state = None if current_package == '' else ParseState.PACKAGE
current_doc = []

# Package contains type, parse it
elif (state is None or state is ParseState.PACKAGE) and line.lower().startswith('type'):
elif (state is None or state is ParseState.PACKAGE) and line_lowercase.startswith('type'):
if ' record' in line.split('--')[0].lower().split(maxsplit=2)[-1]:
parse_inline_doc_or_print_error(current_doc, filename, line, lineno)
records[line.split()[1]] = current_doc
Expand All @@ -212,7 +203,7 @@ def init(path: str) -> None:
current_doc = []

# Signalization of the end of record
elif state is ParseState.RECORD and line.lower().startswith('end record'):
elif state is ParseState.RECORD and line_lowercase.startswith('end record'):
if current_package != '':
state = ParseState.PACKAGE
else:
Expand All @@ -228,16 +219,16 @@ def init(path: str) -> None:

# Enumarate parsing
elif state is ParseState.ENUM:
if not line.startswith(')'):
if not line_lowercase.startswith(')'):
parse_inline_doc_or_print_error(current_doc, filename, line, lineno)
enumvals[current_type_name][line.split(',')[0]] = current_doc
current_doc = []

# Function parsing
elif line.lower().startswith('function') and line.split('--')[0].strip().endswith(';'):
elif line_lowercase.startswith('function') and line.split('--')[0].strip().endswith(';'):
parse_inline_doc_or_print_error(current_doc, filename, line, lineno)
return_type = '' if 'return' not in line else (line.split('return')[1].strip()[:-1] + '.')
functions[return_type + line.lower().split()[1]] = current_doc
functions[return_type + line_lowercase.split()[1]] = current_doc
current_doc = []

# Ignore others
Expand Down
Loading

0 comments on commit 0e116df

Please sign in to comment.