Skip to content

Commit

Permalink
Refactor to use a utils subpackage
Browse files Browse the repository at this point in the history
Better organization
  • Loading branch information
TDKorn committed Apr 7, 2024
1 parent 3edacfb commit fd60375
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 203 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.. |.~.get_linkcode_resolve| replace:: ``get_linkcode_resolve()``
.. _.~.get_linkcode_resolve: https://sphinx-github-style.readthedocs.io/en/latest/modules.html#sphinx_github_style.__init__.get_linkcode_resolve
.. _.~.get_linkcode_resolve: https://sphinx-github-style.readthedocs.io/en/latest/linkcode.html#sphinx_github_style.utils.linkcode.get_linkcode_resolve
.. |linkcode_blob| replace:: ``linkcode_blob``
.. _linkcode_blob: https://sphinx-github-style.readthedocs.io/en/latest/index.html#confval-linkcode_blob
.. |linkcode_link_text| replace:: ``linkcode_link_text``
Expand Down
7 changes: 3 additions & 4 deletions docs/source/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Using :mod:`sphinx.ext.linkcode`, a ``View on GitHub`` link is added to the doc
.. only:: html

.. autofunction:: sphinx_github_style.__init__.get_repo_dir
.. autofunction:: sphinx_github_style.utils.git.get_repo_dir
:noindex:

.. only:: readme or pypi
Expand Down Expand Up @@ -135,10 +135,9 @@ Syntax Highlighting

.. only:: html

.. literalinclude:: ../../sphinx_github_style/__init__.py
.. literalinclude:: ../../sphinx_github_style/utils/git.py
:language: python
:start-after: # EXAMPLE START
:end-before: # EXAMPLE END
:pyobject: get_repo_dir

.. only:: readme or pypi

Expand Down
7 changes: 7 additions & 0 deletions docs/source/git.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
The ``sphinx_github_style.utils.git`` submodule
================================================

.. automodule:: sphinx_github_style.utils.git
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/source/linkcode.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
The ``sphinx_github_style.utils.linkcode`` submodule
=====================================================

.. automodule:: sphinx_github_style.utils.linkcode
:members:
:undoc-members:
:show-inheritance:
8 changes: 7 additions & 1 deletion docs/source/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ The ``sphinx_github_style`` Package


.. automodule:: sphinx_github_style.__init__
:members:
:members: setup
:undoc-members:

|
Expand All @@ -15,3 +15,9 @@ The ``sphinx_github_style`` Package
add_linkcode_class
github_style
lexer

.. toctree::
:caption: The Utils Subpackage
:titlesonly:

utils
7 changes: 7 additions & 0 deletions docs/source/sphinx.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
The ``sphinx_github_style.utils.sphinx`` submodule
===================================================

.. automodule:: sphinx_github_style.utils.sphinx
:members:
:undoc-members:
:show-inheritance:
17 changes: 17 additions & 0 deletions docs/source/utils.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
The ``sphinx_github_style.utils`` subpackage
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. automodule:: sphinx_github_style.utils
:members:
:undoc-members:
:show-inheritance:

The ``sphinx_github_style.utils`` subpackage contains a variety of helper functions

.. toctree::
:maxdepth: 3
:titlesonly:

git
linkcode
sphinx
202 changes: 5 additions & 197 deletions sphinx_github_style/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import sys
import sphinx
import inspect
import subprocess
from pathlib import Path
from functools import cached_property
from typing import Dict, Any
from sphinx.application import Sphinx
from sphinx.errors import ExtensionError
from typing import Dict, Any, Optional, Callable

__version__ = "1.2.0"
__author__ = 'Adam Korn <hello@dailykitten.net>'
Expand All @@ -15,6 +10,10 @@
from .github_style import GitHubStyle
from .lexer import GitHubLexer

from .utils.linkcode import get_linkcode_url, get_linkcode_revision, get_linkcode_resolve
from .utils.sphinx import get_conf_val, set_conf_val
from .utils.git import get_repo_dir


def setup(app: Sphinx) -> Dict[str, Any]:
app.setup_extension('sphinx.ext.linkcode')
Expand Down Expand Up @@ -53,194 +52,3 @@ def add_static_path(app) -> None:
app.config.html_static_path.append(
str(Path(__file__).parent.joinpath("_static").absolute())
)


def get_linkcode_revision(blob: str) -> str:
"""Get the blob to link to on GitHub
.. note::
The value of ``blob`` can be any of ``"head"``, ``"last_tag"``, or ``"{blob}"``
* ``head`` (default): links to the most recent commit hash; if this commit is tagged, uses the tag instead
* ``last_tag``: links to the most recent commit tag on the currently checked out branch
* ``blob``: links to any blob you want, for example ``"master"`` or ``"v2.0.1"``
"""
if blob == "head":
return get_head()
if blob == 'last_tag':
return get_last_tag()
# Link to the branch/tree/blob you provided, ex. "master"
return blob


def get_head() -> str:
"""Gets the most recent commit hash or tag
:return: The SHA or tag name of the most recent commit, or "master" if the call to git fails.
"""
cmd = "git log -n1 --pretty=%H"
try:
# get most recent commit hash
head = subprocess.check_output(cmd.split()).strip().decode('utf-8')

# if head is a tag, use tag as reference
cmd = "git describe --exact-match --tags " + head
try:
tag = subprocess.check_output(cmd.split(" ")).strip().decode('utf-8')
return tag

except subprocess.CalledProcessError:
return head

except subprocess.CalledProcessError:
print("Failed to get head") # so no head?
return "master"


def get_last_tag() -> str:
"""Get the most recent commit tag on the currently checked out branch
:raises ExtensionError: if no tags exist on the branch
"""
try:
cmd = "git describe --tags --abbrev=0"
return subprocess.check_output(cmd.split(" ")).strip().decode('utf-8')

except subprocess.CalledProcessError:
raise ExtensionError("``sphinx-github-style``: no tags found on current branch")


def get_linkcode_url(blob: Optional[str] = None, context: Optional[Dict] = None, url: Optional[str] = None) -> str:
"""Get the template URL for linking to highlighted GitHub source code with :mod:`sphinx.ext.linkcode`
Formatted into the final link by a ``linkcode_resolve()`` function
:param blob: The Git blob to link to
:param context: The :external+sphinx:confval:`html_context` dictionary
:param url: The base URL of the repository (ex. ``https://github.com/TDKorn/sphinx-github-style``)
"""
if url is None:
if context is None or not all(context.get(key) for key in ("github_user", "github_repo")):
raise ExtensionError(
"sphinx-github-style: config value ``linkcode_url`` is missing")
else:
print(
"sphinx-github-style: config value ``linkcode_url`` is missing. "
"Creating link from ``html_context`` values..."
)
url = f"https://github.com/{context['github_user']}/{context['github_repo']}"

blob = get_linkcode_revision(blob) if blob else context.get('github_version')

if blob is not None:
url = url.strip("/") + f"/blob/{blob}/" # URL should be "https://github.com/user/repo"
else:
raise ExtensionError(
"sphinx-github-style: must provide a blob or GitHub version to link to")

return url + "{filepath}#L{linestart}-L{linestop}"


def get_linkcode_resolve(linkcode_url: str, repo_dir: Optional[Path] = None) -> Callable:
"""Defines and returns a ``linkcode_resolve`` function for your package
Used by default if ``linkcode_resolve`` isn't defined in ``conf.py``
:param linkcode_url: The template URL to use when resolving cross-references with :mod:`sphinx.ext.linkcode`
:param repo_dir: The root directory of the Git repository.
"""
if repo_dir is None:
repo_dir = get_repo_dir()

def linkcode_resolve(domain, info):
"""Returns a link to the source code on GitHub, with appropriate lines highlighted
:By:
Adam Korn (https://github.com/tdkorn)
:Adapted From:
nlgranger/SeqTools (https://github.com/nlgranger/seqtools/blob/master/docs/conf.py)
"""
if domain != 'py' or not info['module']:
return None

modname = info['module']
fullname = info['fullname']

submod = sys.modules.get(modname)
if submod is None:
return None

obj = submod
for part in fullname.split('.'):
try:
obj = getattr(obj, part)
except AttributeError:
return None

if isinstance(obj, property):
obj = obj.fget
elif isinstance(obj, cached_property):
obj = obj.func

try:
modpath = inspect.getsourcefile(inspect.unwrap(obj))
filepath = Path(modpath).relative_to(repo_dir)
if filepath is None:
return
except Exception:
return None

try:
source, lineno = inspect.getsourcelines(obj)
except Exception:
return None

linestart, linestop = lineno, lineno + len(source) - 1

# Example: https://github.com/TDKorn/my-magento/blob/docs/magento/models/model.py#L28-L59
final_link = linkcode_url.format(
filepath=filepath.as_posix(),
linestart=linestart,
linestop=linestop
)
print(f"Final Link for {fullname}: {final_link}")
return final_link

return linkcode_resolve


# EXAMPLE START
def get_repo_dir() -> Path:
"""Returns the root directory of the repository
:return: A Path object representing the working directory of the repository.
"""
try:
cmd = "git rev-parse --show-toplevel"
repo_dir = Path(subprocess.check_output(cmd.split(" ")).strip().decode('utf-8'))

except subprocess.CalledProcessError as e:
raise RuntimeError("Unable to determine the repository directory") from e

return repo_dir
# EXAMPLE END


def get_conf_val(app: Sphinx, attr: str, default: Optional[Any] = None) -> Any:
"""Retrieve the value of a ``conf.py`` config variable
:param attr: the config variable to retrieve
:param default: the default value to return if the variable isn't found
"""
return app.config._raw_config.get(attr, getattr(app.config, attr, default))


def set_conf_val(app: Sphinx, attr: str, value: Any) -> None:
"""Set the value of a ``conf.py`` config variable
:param attr: the config variable to set
:param value: the variable value
"""
app.config._raw_config[attr] = value
setattr(app.config, attr, value)
Empty file.
55 changes: 55 additions & 0 deletions sphinx_github_style/utils/git.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import subprocess
from pathlib import Path
from sphinx.errors import ExtensionError


def get_head() -> str:
"""Gets the most recent commit hash or tag
:return: The SHA or tag name of the most recent commit, or "master" if the call to git fails.
"""
cmd = "git log -n1 --pretty=%H"
try:
# get most recent commit hash
head = subprocess.check_output(cmd.split()).strip().decode('utf-8')

# if head is a tag, use tag as reference
cmd = "git describe --exact-match --tags " + head
try:
tag = subprocess.check_output(cmd.split(" ")).strip().decode('utf-8')
return tag

except subprocess.CalledProcessError:
return head

except subprocess.CalledProcessError:
print("Failed to get head") # so no head?
return "master"


def get_last_tag() -> str:
"""Get the most recent commit tag on the currently checked out branch
:raises ExtensionError: if no tags exist on the branch
"""
try:
cmd = "git describe --tags --abbrev=0"
return subprocess.check_output(cmd.split(" ")).strip().decode('utf-8')

except subprocess.CalledProcessError:
raise ExtensionError("``sphinx-github-style``: no tags found on current branch")


def get_repo_dir() -> Path:
"""Returns the root directory of the repository
:return: A Path object representing the working directory of the repository.
"""
try:
cmd = "git rev-parse --show-toplevel"
repo_dir = Path(subprocess.check_output(cmd.split(" ")).strip().decode('utf-8'))

except subprocess.CalledProcessError as e:
raise RuntimeError("Unable to determine the repository directory") from e

return repo_dir
Loading

0 comments on commit fd60375

Please sign in to comment.