diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..95a2e886 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,57 @@ +name: Build & Publish Docs + +on: + push: + branches: + - main + - develop + - documentation # just for testing + pull_request: + +jobs: + docs: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ "3.10.11" ] + max-parallel: 5 + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install sphinx sphinx_rtd_theme sphinx-autoapi sphinx-theme sphinxcontrib-plantuml plantuml-local-client myst-parser + + - name: Prepare required software + run: | + # epstopdf & dot & noto-fonts + sudo apt update && sudo apt install texlive-font-utils graphviz fonts-noto\ + + - name: Build docs + run: | + sphinx-build -M html docs/ docs/_build/ + + - name: Build LaTeX docs + run: | + sphinx-build -M latex docs/ docs/_build/ + + - name: Compile LaTeX document + uses: docker://texlive/texlive:latest + with: + args: make -C docs/_build/latex + - name: Copy Latex pdf to ./html + run: | + cp docs/_build/latex/owlapy.pdf docs/_build/html/ + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: 'docs/_build/html' \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4224f44d --- /dev/null +++ b/.gitignore @@ -0,0 +1,148 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# 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.json +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/ +docs/_static_gen/ +# managed by sphinx autosummary in api.rst +docs/source/ + +# 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 + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__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 IDE +.idea/ + +# VS Code +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index 4512ebd2..ee3dd83a 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,10 @@ In this example we start with a simple atomic class expression and move to some ones and finally render and print the last of them in description logics syntax. ```python -from owlapy.render import DLSyntaxObjectRenderer from owlapy.model import IRI, OWLClass, OWLObjectProperty, OWLObjectSomeValuesFrom, \ OWLObjectIntersectionOf from owlapy.owl2sparql.converter import owl_expression_to_sparql +from owlapy.render import owl_expression_to_dl # Create an IRI object using the iri as a string for 'male' class. male_iri = IRI.create('http://example.com/society#male') @@ -42,8 +42,8 @@ males_with_children = OWLObjectSomeValuesFrom(hasChild, male) teacher = OWLClass(IRI.create('http://example.com/society#teacher')) male_teachers_with_children = OWLObjectIntersectionOf([males_with_children, teacher]) -# You can render and print owl class expressions in description logics syntax -print(DLSyntaxObjectRenderer().render(male_teachers_with_children)) +# You can render and print owl class expressions in description logics syntax (and vice-versa) +print(owl_expression_to_dl(male_teachers_with_children)) # (∃ hasChild.male) ⊓ teacher print(owl_expression_to_sparql("?x", male_teachers_with_children)) # SELECT DISTINCT ?x WHERE { ?x ?s_1 . ?s_1 a . ?x a . } } diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/documentation_options_patch.js b/docs/_static/documentation_options_patch.js new file mode 100644 index 00000000..c43a72bb --- /dev/null +++ b/docs/_static/documentation_options_patch.js @@ -0,0 +1 @@ +DOCUMENTATION_OPTIONS.LINK_SUFFIX = DOCUMENTATION_OPTIONS.FILE_SUFFIX diff --git a/docs/_static/js/theme.js b/docs/_static/js/theme.js new file mode 100644 index 00000000..2f013342 --- /dev/null +++ b/docs/_static/js/theme.js @@ -0,0 +1,243 @@ +var jQuery = (typeof(window) != 'undefined') ? window.jQuery : require('jquery'); + +// Sphinx theme nav state +function ThemeNav () { + + var nav = { + navBar: null, + win: null, + winScroll: false, + winResize: false, + linkScroll: false, + winPosition: 0, + winHeight: null, + docHeight: null, + isRunning: false + }; + + nav.enable = function (withStickyNav) { + var self = this; + + // TODO this can likely be removed once the theme javascript is broken + // out from the RTD assets. This just ensures old projects that are + // calling `enable()` get the sticky menu on by default. All other cals + // to `enable` should include an argument for enabling the sticky menu. + if (typeof(withStickyNav) == 'undefined') { + withStickyNav = true; + } + + if (self.isRunning) { + // Only allow enabling nav logic once + return; + } + + self.isRunning = true; + jQuery(function ($) { + self.init($); + + self.reset(); + self.win.on('hashchange', self.reset); + + if (withStickyNav) { + // Set scroll monitor + self.win.on('scroll', function () { + if (!self.linkScroll) { + if (!self.winScroll) { + self.winScroll = true; + requestAnimationFrame(function() { self.onScroll(); }); + } + } + }); + } + + // Set resize monitor + self.win.on('resize', function () { + if (!self.winResize) { + self.winResize = true; + requestAnimationFrame(function() { self.onResize(); }); + } + }); + + self.onResize(); + }); + + }; + + // TODO remove this with a split in theme and Read the Docs JS logic as + // well, it's only here to support 0.3.0 installs of our theme. + nav.enableSticky = function() { + this.enable(true); + }; + + nav.init = function ($) { + var doc = $(document), + self = this; + + this.navBar = $('div.wy-side-scroll:first'); + this.win = $(window); + + // Set up javascript UX bits + $(document) + // Shift nav in mobile when clicking the menu. + .on('click', "[data-toggle='wy-nav-top']", function() { + $("[data-toggle='wy-nav-shift']").toggleClass("shift"); + $("[data-toggle='rst-versions']").toggleClass("shift"); + }) + + // Nav menu link click operations + .on('click', ".wy-menu-vertical .current ul li a", function() { + var target = $(this); + // Close menu when you click a link. + $("[data-toggle='wy-nav-shift']").removeClass("shift"); + $("[data-toggle='rst-versions']").toggleClass("shift"); + // Handle dynamic display of l3 and l4 nav lists + self.toggleCurrent(target); + self.hashChange(); + }) + .on('click', "[data-toggle='rst-current-version']", function() { + $("[data-toggle='rst-versions']").toggleClass("shift-up"); + }) + + // Make tables responsive + $("table.docutils:not(.field-list,.footnote,.citation)") + .wrap("
"); + + // Add extra class to responsive tables that contain + // footnotes or citations so that we can target them for styling + $("table.docutils.footnote") + .wrap("
"); + $("table.docutils.citation") + .wrap("
"); + + // Add expand links to all parents of nested ul + $('.wy-menu-vertical ul').not('.simple').siblings('a').each(function () { + var link = $(this); + expand = $(''); + expand.on('click', function (ev) { + self.toggleCurrent(link); + ev.stopPropagation(); + return false; + }); + link.prepend(expand); + }); + }; + + nav.reset = function () { + // Get anchor from URL and open up nested nav + var anchor = encodeURI(window.location.hash) || '#'; + + try { + var vmenu = $('.wy-menu-vertical'); + var link = vmenu.find('[href="' + anchor + '"]'); + if (link.length === 0) { + // this link was not found in the sidebar. + // Find associated id element, then its closest section + // in the document and try with that one. + var id_elt = $('.document [id="' + anchor.substring(1) + '"]'); + var closest_section = id_elt.closest('div.section'); + link = vmenu.find('[href="#' + closest_section.attr("id") + '"]'); + if (link.length === 0) { + // still not found in the sidebar. fall back to main section + link = vmenu.find('[href="#"]'); + } + } + // If we found a matching link then reset current and re-apply + // otherwise retain the existing match + if (link.length > 0) { + $('.wy-menu-vertical .current').removeClass('current'); + link.addClass('current'); + link.closest('li.toctree-l1').parent().addClass('current'); + for (let i = 1; i <= 10; i++) { + link.closest('li.toctree-l' + i).addClass('current'); + } + link[0].scrollIntoView(); + } + } + catch (err) { + console.log("Error expanding nav for anchor", err); + } + + }; + + nav.onScroll = function () { + this.winScroll = false; + var newWinPosition = this.win.scrollTop(), + winBottom = newWinPosition + this.winHeight, + navPosition = this.navBar.scrollTop(), + newNavPosition = navPosition + (newWinPosition - this.winPosition); + if (newWinPosition < 0 || winBottom > this.docHeight) { + return; + } + this.navBar.scrollTop(newNavPosition); + this.winPosition = newWinPosition; + }; + + nav.onResize = function () { + this.winResize = false; + this.winHeight = this.win.height(); + this.docHeight = $(document).height(); + }; + + nav.hashChange = function () { + this.linkScroll = true; + this.win.one('hashchange', function () { + this.linkScroll = false; + }); + }; + + nav.toggleCurrent = function (elem) { + var parent_li = elem.closest('li'); + parent_li.siblings('li.current').removeClass('current'); + parent_li.siblings().find('li.current').removeClass('current'); + var children = parent_li.find('> ul li'); + // Don't toggle terminal elements. + if (children.length) { + children.removeClass('current'); + parent_li.toggleClass('current'); + } + } + + return nav; +}; + +module.exports.ThemeNav = ThemeNav(); + +if (typeof(window) != 'undefined') { + window.SphinxRtdTheme = { + Navigation: module.exports.ThemeNav, + // TODO remove this once static assets are split up between the theme + // and Read the Docs. For now, this patches 0.3.0 to be backwards + // compatible with a pre-0.3.0 layout.html + StickyNav: module.exports.ThemeNav, + }; +} + + +// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel +// https://gist.github.com/paulirish/1579671 +// MIT license + +(function() { + var lastTime = 0; + var vendors = ['ms', 'moz', 'webkit', 'o']; + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; + window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] + || window[vendors[x]+'CancelRequestAnimationFrame']; + } + + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function(id) { + clearTimeout(id); + }; +}()); diff --git a/docs/_static/theme_tweak.css b/docs/_static/theme_tweak.css new file mode 100644 index 00000000..aeedc7fa --- /dev/null +++ b/docs/_static/theme_tweak.css @@ -0,0 +1,9 @@ +.rst-content dl:not(.docutils) dl dl dt { + width: auto; +} +.rst-content dl:not(.docutils) dl dt { + width: 96%; +} +.rst-content dl:not(.docutils) dt { + width: 100%; +} diff --git a/docs/_templates/search.html b/docs/_templates/search.html new file mode 100644 index 00000000..07a4b787 --- /dev/null +++ b/docs/_templates/search.html @@ -0,0 +1,2 @@ +{% extends "!search.html" %} +{% set script_files = [ '_static/documentation_options_patch.js', '_static/language_data.js' ] + script_files %} diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..bf08a50a --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,114 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +import os +import sys + +sys.path.insert(0, os.path.abspath("..")) + +project = 'OWLAPY' +author = 'Ontolearn Team' +release = '0.1.2' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = ["autoapi.extension", + "sphinx.ext.githubpages", + "sphinx.ext.todo", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", + "sphinx.ext.autodoc", + "sphinxcontrib.plantuml", + "myst_parser", + "sphinx_rtd_theme", + ] + + +autoapi_dirs = ['../owlapy'] + +# by default all are included but had to reinitialize this to remove private members from showing +autoapi_options = ['members', 'undoc-members', 'show-inheritance', 'show-module-summary', 'special-members', + 'imported-members'] + +# this is set to false, so we can add it manually in index.rst together with the other .md files of the documentation. +autoapi_add_toctree_entry = False + +inheritance_graph_attrs = dict(rankdir="TB") + +myst_enable_extensions = [ + 'colon_fence', + 'deflist', +] + +myst_heading_anchors = 3 + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +pygments_style = 'rainbow_dash' + +plantuml_output_format = 'svg_img' +plantuml_latex_output_format = 'pdf' + +stanford_theme_mod = True +html_theme_options = { + 'navigation_depth': 6, +} + +html_static_path = ['_static'] + +if stanford_theme_mod: + html_theme = 'sphinx_rtd_theme' + + def _import_theme(): + import os + import shutil + import sphinx_theme + html_theme = 'stanford_theme' + for _type in ['fonts']: + shutil.copytree( + os.path.join(sphinx_theme.get_html_theme_path(html_theme), + html_theme, 'static', _type), + os.path.join('_static_gen', _type), + dirs_exist_ok=True) + shutil.copy2( + os.path.join(sphinx_theme.get_html_theme_path(html_theme), + html_theme, 'static', 'css', 'theme.css'), + os.path.join('_static_gen', 'theme.css'), + ) + + _import_theme() + html_static_path = ['_static_gen'] + html_static_path + +# -- Options for LaTeX output ------------------------------------------------ + +latex_engine = 'xelatex' +latex_show_urls = 'footnote' +latex_theme = 'howto' + +latex_elements = { + 'preamble': r''' +\renewcommand{\pysiglinewithargsret}[3]{% + \item[{% + \parbox[t]{\linewidth}{\setlength{\hangindent}{12ex}% + \raggedright#1\sphinxcode{(}\linebreak[0]{\renewcommand{\emph}[1]{\mbox{\textit{##1}}}#2}\sphinxcode{)}\linebreak[0]\mbox{#3}}}]} +''', + 'printindex': '\\def\\twocolumn[#1]{#1}\\footnotesize\\raggedright\\printindex', +} + + +def setup(app): + # -- Options for HTML output --------------------------------------------- + if stanford_theme_mod: + app.add_css_file('theme.css') + app.add_css_file('theme_tweak.css') + app.add_css_file('pygments.css') \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..3e4858f3 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,14 @@ + + +Welcome to OWLAPY! +=========================================== + +`OWLAPY `_: Representation of OWL objects in python. + + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + usage/main + autoapi/owlapy/index \ No newline at end of file diff --git a/docs/usage/main.md b/docs/usage/main.md new file mode 100644 index 00000000..54b6f2f2 --- /dev/null +++ b/docs/usage/main.md @@ -0,0 +1,3 @@ +# OWLAPY + +placeholder \ No newline at end of file diff --git a/owlapy/parser.py b/owlapy/parser.py index c9a8cae7..551e477e 100644 --- a/owlapy/parser.py +++ b/owlapy/parser.py @@ -767,3 +767,15 @@ def visit_parentheses(self, node, children) -> OWLClassExpression: def generic_visit(self, node, children): return children or node + + +DLparser = DLSyntaxParser() +ManchesterParser = ManchesterOWLSyntaxParser() + + +def dl_to_owl_expression(dl_expression: str): + return DLparser.parse_expression(dl_expression) + + +def manchester_to_owl_expression(manchester_expression: str): + return ManchesterParser.parse_expression(manchester_expression) diff --git a/owlapy/render.py b/owlapy/render.py index 671a9512..c1685fdf 100644 --- a/owlapy/render.py +++ b/owlapy/render.py @@ -424,3 +424,15 @@ def _render_nested(self, c: OWLClassExpression) -> str: return "(%s)" % self.render(c) else: return self.render(c) + + +DLrenderer = DLSyntaxObjectRenderer() +ManchesterRenderer = ManchesterOWLSyntaxOWLObjectRenderer() + + +def owl_expression_to_dl(o: OWLObject) -> str: + return DLrenderer.render(o) + + +def owl_expression_to_manchester(o: OWLObject) -> str: + return ManchesterRenderer.render(o) diff --git a/tests/test_owlapy_render.py b/tests/test_owlapy_render.py index b229ede6..132a61c3 100644 --- a/tests/test_owlapy_render.py +++ b/tests/test_owlapy_render.py @@ -43,6 +43,7 @@ def test_ce_render(self): oneof = OWLObjectOneOf((i1, i2)) r = renderer.render(oneof) print(r) + self.assertEqual(r, "{heinz , marie}") hasvalue = OWLObjectHasValue(property=has_child, individual=i1)