Loading .gitlab-ci.yml 0 → 100644 +69 −0 Original line number Diff line number Diff line stages: - build - release variables: REGISTRY: $CI_REGISTRY IMAGE: $CI_REGISTRY_IMAGE before_script: # Log in to GitLab registry for docker builds - echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER $CI_REGISTRY --password-stdin # -------------------- # Docker builds # -------------------- .build-template: stage: build script: - | if [ -n "$CI_COMMIT_TAG" ]; then VERSION="${CI_COMMIT_TAG#v}" # strip leading v else VERSION="dev-${CI_COMMIT_SHORT_SHA}" fi - docker build --target $TARGET \ -t $IMAGE:$VARIANT \ -t $IMAGE:${VERSION}-${VARIANT} . - docker push $IMAGE:$VARIANT - docker push $IMAGE:${VERSION}-${VARIANT} build:website: extends: .build-template variables: TARGET: website VARIANT: website build:ts: extends: .build-template variables: TARGET: ts VARIANT: ts build:check: extends: .build-template variables: TARGET: check VARIANT: check build:all: extends: .build-template variables: TARGET: all VARIANT: all # -------------------- # Publish to PyPI # -------------------- publish:pypi: stage: release image: python:3.13-slim before_script: - pip install --no-cache-dir uv script: - uv publish --token $TWINE_PASSWORD rules: - if: '$CI_COMMIT_TAG' # only run when a tag is pushed README.md +16 −1 Original line number Diff line number Diff line Loading @@ -28,7 +28,7 @@ pipx install saref-pipeline git clone https://labs.etsi.org/rep/saref/saref-pypeline.git cd saref-pipeline pip install -e .[check,website] pip install -e .[check,website,ts] ## Basic usage Loading Loading @@ -120,3 +120,18 @@ Known limitations: * [] ts generation: missing general axioms * [] publish to pypi. ## For maintainers Docker image tag and library version is automatically computed from git tag. Use too `bump-my-version` to read the Git history/tags, increment `major`, `minor`, or `patch`, and create the corresponding Git tag. ``` uv tools install bump-my-version ``` Then to tag and push a new version (ex. `minor`): ``` bump-my-version bump minor ``` No newline at end of file dockerfile 0 → 100644 +40 −0 Original line number Diff line number Diff line # ---- Base stage ---- FROM python:3.13-slim AS base # Install system dependencies for building + runtime RUN apt-get update && apt-get install -y \ git \ libleveldb-dev \ build-essential \ && rm -rf /var/lib/apt/lists/* # Install uv RUN pip install --no-cache-dir uv WORKDIR /app COPY pyproject.toml uv.lock .python-version ./ COPY src/ src/ RUN uv pip install --system -e . # ---- Website ---- FROM base AS website RUN uv pip install --system -e .[website] ENTRYPOINT ["saref-dev", "website"] # ---- TS ---- FROM base AS ts RUN uv pip install --system -e .[ts] ENTRYPOINT ["saref-dev", "ts"] # ---- Check ---- FROM base AS check # Install Java 21 RUN apt-get update && apt-get install -y openjdk-21-jre-headless && rm -rf /var/lib/apt/lists/* RUN uv pip install --system -e .[check] ENTRYPOINT ["saref-dev", "check"] # ---- All ---- FROM check AS all RUN uv pip install --system -e .[website,ts] ENTRYPOINT ["saref-dev"] No newline at end of file pyproject.toml +9 −2 Original line number Diff line number Diff line [build-system] requires = ["setuptools>=61.0"] requires = ["setuptools>=80", "setuptools-scm[simple]>=8"] build-backend = "setuptools.build_meta" [project] name = "saref-pypeline" version = "0.1" dynamic = ["version"] description = "SAREF pipeline tools" authors = [ { name = "Maxime Lefrançois", email = "maxime.lefrancois@emse.fr" } Loading @@ -24,6 +24,13 @@ dependencies = [ [tool.setuptools.packages.find] where = ["src"] [tool.bumpversion] allow_dirty = false commit = false tag = true tag_name = "v{new_version}" tag_message = "Bump version: {current_version} → {new_version}" [project.scripts] saref-dev = "saref_pypeline.__main__:main" Loading src/saref_pypeline/ts/ts2md_extractor.py +50 −7 Original line number Diff line number Diff line Loading @@ -40,7 +40,8 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) nsmap["v"] = "urn:schemas-microsoft-com:vml" REGEX_CODE_MD = re.compile(r"^(\s*)`(.*)`(\s*)$") REGEX_CODE_HTML = re.compile(r"^(.*)</code></pre>\n$") class ExtractFormat(Enum): HTML = auto() Loading Loading @@ -265,7 +266,7 @@ def extract_run(run: Run, ctx: RunContext): text = run.text.replace(" ", " ") # extract white space before and after before, text, after = re.match(r"^(\W*)(.*?)(\W*)$", text, re.DOTALL).groups() before, text, after = re.match(r"^([^a-zA-Z0-9_®<>\.;,]*)(.*?)([^a-zA-Z0-9_®<>\.;,]*)$", text, re.DOTALL).groups() if before: ctx.buffer_blank.append(before) Loading Loading @@ -473,7 +474,43 @@ class TS2MDExtractor: for block_item in iter_block_items(self.document, text, include_title): md = self.extract_block_item(block_item) if md: md_output.append(md) for line in md.split("\n"): md_output.append(line) # pre code in examples for i, md in enumerate(md_output): if i==0: continue if m0:=re.match(REGEX_CODE_MD, md): if m1:=re.match(REGEX_CODE_MD, md_output[i-1]): m1s, m1t, m1e = m1.groups() m0s, m0t, m0e = m0.groups() m1t = m1t.replace("&", "&").replace("<", "<") m0t = m0t.replace("&", "&").replace("<", "<") md_output[i-1] = f"""\n{m1s}<pre><code class="language-turtle">{m1t}{m1e}""" md_output[i] = f"""{m0s}{m0t}{m0e}</code></pre>\n""" elif m1:=re.match(REGEX_CODE_HTML, md_output[i-1]): md_output[i-1] = m1.group(1) m0s, m0t, m0e = m0.groups() m0t = m0t.replace("&", "&").replace("<", "<") md_output[i] = f"""{m0s}{m0t}{m0e}</code></pre>\n""" # code with newline open = False for i, md in enumerate(md_output): if i==0: continue if open and (m:=re.match(r"^([^`]*)`(\s*)$", md)): open = False mt, me = m.groups() mt = mt.replace("&", "&").replace("<", "<") md_output[i] = f"""{mt}</code></pre>{me}""" if not open and (m:=re.match(r"^(\s*)`([^`]*)$", md)): open = True ms, mt = m.groups() mt = mt.replace("&", "&").replace("<", "<") md_output[i] = f"""\n{ms}<pre><code class="language-turtle">{mt}""" return "\n".join(md_output) def extract_block_item(self, block_item: Table | Paragraph) -> str: Loading Loading @@ -595,6 +632,9 @@ class TS2MDExtractor: def extract_heading(self, paragraph: Paragraph, prefix: str): md = self.extract_inner_content(paragraph) if match := re.match(r"^\d+(\.\d+)* *\t(.*)", md): if match.group(1)==".0": md = "<mark/>" + match.group(2) else: md = match.group(2) elif match := re.match(r"^Annex.*?\n(.*)", md): md = match.group(1) Loading Loading @@ -651,10 +691,13 @@ class TS2MDExtractor: # Example left, right = md.split("\t", 1) content = right.replace("\n", "\n ") return f'!!! example "{left}"\n{content}\n' return f'!!! example "{left}"\n {content}' elif md.startswith("["): # Reference return f"* {md}" elif m:=re.match(r"^(\s*)`(.*)`([\s,;\.\(\)\[\]]*)$", md, re.DOTALL): # line of code in example return f" `"+ "`\n `".join(f"{m.group(1)}{m.group(2)}{m.group(3)}".split("\n")) + "`" else: # last item of abbreviations: left, right = md.split("\t", 1) Loading Loading @@ -759,7 +802,7 @@ class TS2MDExtractor: self, paragraph: Paragraph, extract_format: ExtractFormat = ExtractFormat.MD ): """Bulleted indent 3 (square bullets)""" return self.extract_B_plus(paragraph, 8, extract_format) return self.extract_B_plus(paragraph, 6, extract_format) def extract_BN( self, paragraph: Paragraph, extract_format: ExtractFormat = ExtractFormat.MD Loading Loading @@ -1154,7 +1197,7 @@ class TS2MDExtractor: def print_admonition(classes: str, title: str, md: str): prefix = 4 * " " content = md.replace("\n", f"\n{prefix}") content = md.replace("\n", f"\n{prefix}").lstrip() return f'\n!!! {classes} "{title}"\n{prefix}{content}\n' Loading Loading
.gitlab-ci.yml 0 → 100644 +69 −0 Original line number Diff line number Diff line stages: - build - release variables: REGISTRY: $CI_REGISTRY IMAGE: $CI_REGISTRY_IMAGE before_script: # Log in to GitLab registry for docker builds - echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER $CI_REGISTRY --password-stdin # -------------------- # Docker builds # -------------------- .build-template: stage: build script: - | if [ -n "$CI_COMMIT_TAG" ]; then VERSION="${CI_COMMIT_TAG#v}" # strip leading v else VERSION="dev-${CI_COMMIT_SHORT_SHA}" fi - docker build --target $TARGET \ -t $IMAGE:$VARIANT \ -t $IMAGE:${VERSION}-${VARIANT} . - docker push $IMAGE:$VARIANT - docker push $IMAGE:${VERSION}-${VARIANT} build:website: extends: .build-template variables: TARGET: website VARIANT: website build:ts: extends: .build-template variables: TARGET: ts VARIANT: ts build:check: extends: .build-template variables: TARGET: check VARIANT: check build:all: extends: .build-template variables: TARGET: all VARIANT: all # -------------------- # Publish to PyPI # -------------------- publish:pypi: stage: release image: python:3.13-slim before_script: - pip install --no-cache-dir uv script: - uv publish --token $TWINE_PASSWORD rules: - if: '$CI_COMMIT_TAG' # only run when a tag is pushed
README.md +16 −1 Original line number Diff line number Diff line Loading @@ -28,7 +28,7 @@ pipx install saref-pipeline git clone https://labs.etsi.org/rep/saref/saref-pypeline.git cd saref-pipeline pip install -e .[check,website] pip install -e .[check,website,ts] ## Basic usage Loading Loading @@ -120,3 +120,18 @@ Known limitations: * [] ts generation: missing general axioms * [] publish to pypi. ## For maintainers Docker image tag and library version is automatically computed from git tag. Use too `bump-my-version` to read the Git history/tags, increment `major`, `minor`, or `patch`, and create the corresponding Git tag. ``` uv tools install bump-my-version ``` Then to tag and push a new version (ex. `minor`): ``` bump-my-version bump minor ``` No newline at end of file
dockerfile 0 → 100644 +40 −0 Original line number Diff line number Diff line # ---- Base stage ---- FROM python:3.13-slim AS base # Install system dependencies for building + runtime RUN apt-get update && apt-get install -y \ git \ libleveldb-dev \ build-essential \ && rm -rf /var/lib/apt/lists/* # Install uv RUN pip install --no-cache-dir uv WORKDIR /app COPY pyproject.toml uv.lock .python-version ./ COPY src/ src/ RUN uv pip install --system -e . # ---- Website ---- FROM base AS website RUN uv pip install --system -e .[website] ENTRYPOINT ["saref-dev", "website"] # ---- TS ---- FROM base AS ts RUN uv pip install --system -e .[ts] ENTRYPOINT ["saref-dev", "ts"] # ---- Check ---- FROM base AS check # Install Java 21 RUN apt-get update && apt-get install -y openjdk-21-jre-headless && rm -rf /var/lib/apt/lists/* RUN uv pip install --system -e .[check] ENTRYPOINT ["saref-dev", "check"] # ---- All ---- FROM check AS all RUN uv pip install --system -e .[website,ts] ENTRYPOINT ["saref-dev"] No newline at end of file
pyproject.toml +9 −2 Original line number Diff line number Diff line [build-system] requires = ["setuptools>=61.0"] requires = ["setuptools>=80", "setuptools-scm[simple]>=8"] build-backend = "setuptools.build_meta" [project] name = "saref-pypeline" version = "0.1" dynamic = ["version"] description = "SAREF pipeline tools" authors = [ { name = "Maxime Lefrançois", email = "maxime.lefrancois@emse.fr" } Loading @@ -24,6 +24,13 @@ dependencies = [ [tool.setuptools.packages.find] where = ["src"] [tool.bumpversion] allow_dirty = false commit = false tag = true tag_name = "v{new_version}" tag_message = "Bump version: {current_version} → {new_version}" [project.scripts] saref-dev = "saref_pypeline.__main__:main" Loading
src/saref_pypeline/ts/ts2md_extractor.py +50 −7 Original line number Diff line number Diff line Loading @@ -40,7 +40,8 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) nsmap["v"] = "urn:schemas-microsoft-com:vml" REGEX_CODE_MD = re.compile(r"^(\s*)`(.*)`(\s*)$") REGEX_CODE_HTML = re.compile(r"^(.*)</code></pre>\n$") class ExtractFormat(Enum): HTML = auto() Loading Loading @@ -265,7 +266,7 @@ def extract_run(run: Run, ctx: RunContext): text = run.text.replace(" ", " ") # extract white space before and after before, text, after = re.match(r"^(\W*)(.*?)(\W*)$", text, re.DOTALL).groups() before, text, after = re.match(r"^([^a-zA-Z0-9_®<>\.;,]*)(.*?)([^a-zA-Z0-9_®<>\.;,]*)$", text, re.DOTALL).groups() if before: ctx.buffer_blank.append(before) Loading Loading @@ -473,7 +474,43 @@ class TS2MDExtractor: for block_item in iter_block_items(self.document, text, include_title): md = self.extract_block_item(block_item) if md: md_output.append(md) for line in md.split("\n"): md_output.append(line) # pre code in examples for i, md in enumerate(md_output): if i==0: continue if m0:=re.match(REGEX_CODE_MD, md): if m1:=re.match(REGEX_CODE_MD, md_output[i-1]): m1s, m1t, m1e = m1.groups() m0s, m0t, m0e = m0.groups() m1t = m1t.replace("&", "&").replace("<", "<") m0t = m0t.replace("&", "&").replace("<", "<") md_output[i-1] = f"""\n{m1s}<pre><code class="language-turtle">{m1t}{m1e}""" md_output[i] = f"""{m0s}{m0t}{m0e}</code></pre>\n""" elif m1:=re.match(REGEX_CODE_HTML, md_output[i-1]): md_output[i-1] = m1.group(1) m0s, m0t, m0e = m0.groups() m0t = m0t.replace("&", "&").replace("<", "<") md_output[i] = f"""{m0s}{m0t}{m0e}</code></pre>\n""" # code with newline open = False for i, md in enumerate(md_output): if i==0: continue if open and (m:=re.match(r"^([^`]*)`(\s*)$", md)): open = False mt, me = m.groups() mt = mt.replace("&", "&").replace("<", "<") md_output[i] = f"""{mt}</code></pre>{me}""" if not open and (m:=re.match(r"^(\s*)`([^`]*)$", md)): open = True ms, mt = m.groups() mt = mt.replace("&", "&").replace("<", "<") md_output[i] = f"""\n{ms}<pre><code class="language-turtle">{mt}""" return "\n".join(md_output) def extract_block_item(self, block_item: Table | Paragraph) -> str: Loading Loading @@ -595,6 +632,9 @@ class TS2MDExtractor: def extract_heading(self, paragraph: Paragraph, prefix: str): md = self.extract_inner_content(paragraph) if match := re.match(r"^\d+(\.\d+)* *\t(.*)", md): if match.group(1)==".0": md = "<mark/>" + match.group(2) else: md = match.group(2) elif match := re.match(r"^Annex.*?\n(.*)", md): md = match.group(1) Loading Loading @@ -651,10 +691,13 @@ class TS2MDExtractor: # Example left, right = md.split("\t", 1) content = right.replace("\n", "\n ") return f'!!! example "{left}"\n{content}\n' return f'!!! example "{left}"\n {content}' elif md.startswith("["): # Reference return f"* {md}" elif m:=re.match(r"^(\s*)`(.*)`([\s,;\.\(\)\[\]]*)$", md, re.DOTALL): # line of code in example return f" `"+ "`\n `".join(f"{m.group(1)}{m.group(2)}{m.group(3)}".split("\n")) + "`" else: # last item of abbreviations: left, right = md.split("\t", 1) Loading Loading @@ -759,7 +802,7 @@ class TS2MDExtractor: self, paragraph: Paragraph, extract_format: ExtractFormat = ExtractFormat.MD ): """Bulleted indent 3 (square bullets)""" return self.extract_B_plus(paragraph, 8, extract_format) return self.extract_B_plus(paragraph, 6, extract_format) def extract_BN( self, paragraph: Paragraph, extract_format: ExtractFormat = ExtractFormat.MD Loading Loading @@ -1154,7 +1197,7 @@ class TS2MDExtractor: def print_admonition(classes: str, title: str, md: str): prefix = 4 * " " content = md.replace("\n", f"\n{prefix}") content = md.replace("\n", f"\n{prefix}").lstrip() return f'\n!!! {classes} "{title}"\n{prefix}{content}\n' Loading