initial rspec implementation

This commit is contained in:
Dirk Alders 2025-03-17 12:34:15 +01:00
parent cfd4088537
commit 1a941be7e0
8 changed files with 310 additions and 0 deletions

37
Makefile Normal file
View File

@ -0,0 +1,37 @@
TARGET=specification
MODULE_NAME := $(shell basename `dirname \`pwd\``)
.PHONY: all venv3 tex pdf clean cleanall
all: release
venv3:
virtualenv -p /usr/bin/python3 venv3
venv3/bin/pip install --upgrade pip
venv3/bin/pip install -r requirements.txt
tex: venv3
venv3/bin/python3 rspec/__init__.py $(MODULE_NAME)/_requirements_/$(TARGET).py > $(TARGET).tex
pdf: tex
latexmk -pdf -pdflatex="pdflatex -interaction=nonstopmode" -use-make $(TARGET).tex
release: pdf clean
mkdir -p ../pylibs/$(MODULE_NAME)/_requirements_
mv specification.pdf ../pylibs/$(MODULE_NAME)/_requirements_
view: pdf clean
xdg-open $(TARGET).pdf
clean:
@echo "\033[1;33mCleaning up requirements...\033[00m"
@echo "\e[1m * Generated latex files...\e[0m"
@latexmk -c -f $(TARGET).tex 1> /dev/null 2> /dev/null
@echo "\e[1m * Generated TeX-File...\e[0m"
@rm -vf *.tex
@rm -rf venv
cleanall: clean
@echo "\e[1m * Generated pdf-File...\e[0m"
@rm -vf $(TARGET).pdf

100
__init__.py Normal file
View File

@ -0,0 +1,100 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import jinja2
import optparse
import os
import sys
import importlib
BASEPATH = os.path.abspath(os.path.join(os.path.dirname(__file__)))
sys.path.insert(0, BASEPATH)
class rspec(dict):
KEY_MAIN_TITLE = "title"
KEY_MAIN_ENTRIES = "entries"
KEY_MAIN_SECTIONS = "sections"
KEY_SEC_CHILDS = "childs"
def __init__(self):
super().__init__()
self[self.KEY_MAIN_ENTRIES] = {}
self[self.KEY_MAIN_SECTIONS] = []
def add_title(self, title):
self[self.KEY_MAIN_TITLE] = title
def add(self, **kwargs):
#
# UUID
#
try:
uuid = kwargs.pop("itemtype") + "-" + "%04d" % kwargs.pop("id")
except KeyError:
raise KeyError(
"Missing required argument itemtype or id in kwargs: %s" % repr(kwargs))
#
# Check uuid is unique
#
if uuid in self[self.KEY_MAIN_ENTRIES]:
raise AttributeError("UUID %s already exists in entries" % uuid)
#
# Parent
#
try:
parent = kwargs.pop("parent")
except KeyError:
if not uuid.lower().startswith("sec"):
raise KeyError(
"You need to specify a parent for non sec entries")
#
# Create Structure
#
if uuid.lower().startswith("sec"):
# Create section
self[self.KEY_MAIN_SECTIONS].append(uuid)
else:
# Add item to section
self[self.KEY_MAIN_ENTRIES][parent][self.KEY_SEC_CHILDS].append(uuid)
#
# Add item to itemlist
#
if uuid.lower().startswith("sec"):
kwargs[self.KEY_SEC_CHILDS] = []
self[self.KEY_MAIN_ENTRIES][uuid] = kwargs
#
return uuid
def rs_by_spec_file(spec_file):
spec = importlib.util.spec_from_file_location("specification", spec_file)
s = importlib.util.module_from_spec(spec)
sys.modules["specification"] = s
spec.loader.exec_module(s)
#
rs = rspec()
s.specification(rs)
return rs
if __name__ == '__main__':
parser = optparse.OptionParser("usage: %prog reqif_file")
parser.add_option("-t", "--template", dest="template", default=os.path.join(BASEPATH,
'templates', 'tex', 'requirement_specification.tex'), help="path to the template")
(options, args) = parser.parse_args()
#
# Check options and args
if len(args) != 1 or not os.path.isfile(args[0]):
parser.print_help()
else:
rs = rs_by_spec_file(args[0])
#
template_path = os.path.abspath(options.template)
jenv = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(template_path)))
template = jenv.get_template(os.path.basename(template_path))
print(template.render(data=rs))

1
templates/tex/foot.tex Normal file
View File

@ -0,0 +1 @@
\end{document}

96
templates/tex/head.tex Normal file
View File

@ -0,0 +1,96 @@
{%- import 'macros.tex' as macros %}
\documentclass[a4paper]{article}
%\documentclass[a4paper,landscape]{article}
\renewcommand{\familydefault}{\sfdefault}
\usepackage[table]{xcolor}
\definecolor{orange}{rgb}{1, 0.7, 0}
\definecolor{lightgrey}{rgb}{0.925, 0.925, 0.925}
\setlength{\topmargin}{-3cm}
\setlength{\oddsidemargin}{-0.5cm}
\setlength{\evensidemargin}{0cm}
\setlength{\textwidth}{17.5cm}
\setlength{\textheight}{24.5cm}
%\setlength{\textwidth}{25cm}
%\setlength{\textheight}{15cm}
\setlength{\headheight}{84pt}
\usepackage{fancyvrb}
\usepackage{fvextra}
%\usepackage{framed,color}
%\newenvironment{modulelog}{\snugshade\Verbatim}{\endVerbatim\endsnugshade}
\usepackage{adjustbox}
\newenvironment{modulelog}%
{\par\noindent\adjustbox{margin=0ex,bgcolor=shadecolor,margin=0ex}\bgroup\varwidth\linewidth\Verbatim}%
{\endVerbatim\endvarwidth\egroup}
%\usepackage{xcolor}
\renewcommand{\baselinestretch}{1,2}
\setlength{\parindent}{0pt}
\setlength{\parskip}{9pt plus3pt minus3pt}
\usepackage{listings}
\usepackage{color}
\definecolor{bg-partially-covered}{rgb}{1,1,0.6} % light-yellow
\definecolor{bg-uncovered}{rgb}{1,0.8,0.8} % light-red
\definecolor{bg-covered}{rgb}{0.95,1,0.95} % very light-green
\definecolor{bg-clean}{rgb}{1,1,1} % white
\definecolor{mygreen}{rgb}{0,0.6,0}
\definecolor{mygray}{rgb}{0.5,0.5,0.5}
\definecolor{mymauve}{rgb}{0.58,0,0.82}
\lstset{ %
backgroundcolor=\color{white}, % choose the background color; you must add \usepackage{color} or \usepackage{xcolor}; should come as last argument
basicstyle=\footnotesize, % the size of the fonts that are used for the code
breakatwhitespace=false, % sets if automatic breaks should only happen at whitespace
breaklines=true, % sets automatic line breaking
captionpos=b, % sets the caption-position to bottom
commentstyle=\color{mygreen}, % comment style
deletekeywords={...}, % if you want to delete keywords from the given language
escapeinside={\%*}{*)}, % if you want to add LaTeX within your code
extendedchars=true, % lets you use non-ASCII characters; for 8-bits encodings only, does not work with UTF-8
frame=none, % adds a frame around the code
keepspaces=true, % keeps spaces in text, useful for keeping indentation of code (possibly needs columns=flexible)
keywordstyle=\color{blue}, % keyword style
language=Octave, % the language of the code
morekeywords={*,...}, % if you want to add more keywords to the set
numbers=left, % where to put the line-numbers; possible values are (none, left, right)
numbersep=5pt, % how far the line-numbers are from the code
numberstyle=\tiny\color{mygray}, % the style that is used for the line-numbers
rulecolor=\color{black}, % if not set, the frame-color may be changed on line-breaks within not-black text (e.g. comments (green here))
showlines=true,
showspaces=false, % show spaces everywhere adding particular underscores; it overrides 'showstringspaces'
showstringspaces=false, % underline spaces within strings only
showtabs=false, % show tabs within strings adding particular underscores
stepnumber=1, % the step between two line-numbers. If it's 1, each line will be numbered
stringstyle=\color{mymauve}, % string literal style
tabsize=2, % sets default tabsize to 2 spaces
}
\usepackage{hyperref}
\usepackage{longtable}
\usepackage{tabu}
\usepackage{multicol}
\usepackage{booktabs}
\usepackage{graphicx}
\usepackage{lastpage} % for the number of the last page in the document
\usepackage{fancyhdr}
\usepackage{xspace}
% define commands for easy access
\newcommand{\req}[2]{\begin{infoBox} \textbf{ #1:} #2 \end{infoBox}}
\newcommand{\rfori}[1]{\textbf{Reason for Implementation:} #1 \\}
\newcommand{\fitcrit}[1]{\textbf{Fitcriterion:} #1 \\}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\renewcommand{\footrulewidth}{0pt}
\lhead{\textcolor{gray}{}}
\chead{\textcolor{gray}{ Requirement Specification for {\tt {{ macros.latex_filter(data.title) }} }}}
\rhead{\textcolor{gray}{}}
\lfoot{\textcolor{gray}{}}
\cfoot{\textcolor{gray}{}}
\rfoot{\textcolor{gray}{\thepage\,/ \pageref{LastPage}}}
\begin{document}

13
templates/tex/macros.tex Normal file
View File

@ -0,0 +1,13 @@
{%- macro requirement_filter(text) -%}{{ text.replace('<', '$<$').replace('>', '$>$').replace('_', '\\_') }}
{%- endmacro -%}
{%- macro latex_filter(text) -%}{{ text.replace('\\', '/').replace('%', '\\%').replace('/xc2/xb0', '$^\circ$').replace('"', '\'').replace('/', '/\\allowbreak ').replace('&', '\\allowbreak \\&').replace('_', '\\_').replace('->', '$\\rightarrow$').replace('<-', '$\\leftarrow$').replace('=>', '$\\Rightarrow$').replace('<=', '$\\leq$').replace('>=', '$\\geq$').replace('<', '$<$').replace('>', '$>$').replace('{', '\{').replace('}', '\}').replace('#', '\\#')}}
{%- endmacro -%}
{%- macro color_by_level(level) -%}{% if level <= 10 %}black{% else %}{% if level <= 20 %}green{% else %}{% if level <= 30 %}orange{% else %}red{% endif %}{% endif %}{% endif %}
{%- endmacro -%}
{%- macro bg_by_levelno(level) -%}{% if level <= 10 %}0.8 0.8 0.8{% else %}{% if level <= 20 %}0.8 0.95 0.8{% else %}{% if level <= 30 %}1 0.75 0.45{% else %}0.95 0.8 0.8{% endif %}{% endif %}{% endif %}
{%- endmacro -%}
{%- macro result(level) -%}{% if level <= 10 %}Info{% else %}{% if level <= 20 %}\textcolor{green}{Success}{% else %}{% if level <= 30 %}\textcolor{orange}{Warning}{% else %}\textcolor{red}{Failed}{% endif %}{% endif %}{% endif %}
{%- endmacro -%}

View File

@ -0,0 +1,15 @@
{%- import 'macros.tex' as macros %}
{%- if 'Description' in item and item.Description != '' %}
\paragraph{\xspace{}{{ macros.latex_filter(item.ID) }} ({{macros.latex_filter(item.Heading)}}):}
{{ macros.requirement_filter(item.Description) }}
{%- endif %}
{%- if 'ReasonForImplementation' in item and item.ReasonForImplementation != '' %}
Reason for Implementation: {{ macros.requirement_filter(item.ReasonForImplementation) }}
{%- endif %}
{%- if 'Fitcriterion' in item and item.Fitcriterion != '' %}
Fitcriterion: {{ macros.requirement_filter(item.Fitcriterion) }}
{%- endif %}

View File

@ -0,0 +1,33 @@
{%- import 'macros.tex' as macros %}
{%- include 'head.tex' %}
{%- include 'titlepage.tex' %}
\tableofcontents
\newpage
{%- for sec_uid in data.sections %}
{% with section=data.entries[sec_uid] %}
\section{ {{ sec_uid }}: {{macros.latex_filter(section.heading)}} }
{%- for req_uid in section.childs %}
{% with item=data.entries[req_uid] %}
\subsection{\xspace{}{{ req_uid }}: {{ macros.latex_filter(item.heading) }} }
{%- if 'description' in item and item.description != '' %}
{{ item.description }}
{%- if 'reason' in item and item.reason != '' or 'fitcriterion' in item and item.fitcriterion != ''%}
\begin{tabu}{lX}
\toprule
{%- if 'reason' in item and item.reason != '' %}
\emph{Reason} & {{ item.reason }}\\
{%- endif %}
{%- if 'fitcriterion' in item and item.fitcriterion != '' %}
\emph{Fitcriterion} & {{ item.fitcriterion }}\\
{%- endif %}
\bottomrule
\end{tabu}
{%- endif %}
{%- endif %}
{% endwith %}
{%- endfor %}
{% endwith %}
{%- endfor %}
{% include 'foot.tex' %}

View File

@ -0,0 +1,15 @@
{%- import 'macros.tex' as macros %}
\begin{titlepage}
\date{\today}
\title{
Requirement Specification for\\{\tt {{ macros.latex_filter(data.title) }} }
}
\date{\today}
\maketitle
\thispagestyle{empty}
\newpage
\end{titlepage}
\setcounter{page}{1}
\pagestyle{fancy}