Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add package to FunctionReference #69

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,28 @@

# -- Project information -----------------------------------------------------

project = 'Research References Tracking Tool (R2T2)'
copyright = '2020, Research Computing Service, Imperial College London'
author = 'Research Computing Service, Imperial College London'
project = "Research References Tracking Tool (R2T2)"
copyright = "2020, Research Computing Service, Imperial College London"
author = "Research Computing Service, Imperial College London"

# The full version, including alpha/beta/rc tags
release = '0.3.1'
release = "0.3.1"


# -- General configuration ---------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
]
extensions = []

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

# The master toctree document.
master_doc = "index"
Expand All @@ -49,9 +48,9 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
html_theme = "alabaster"

# 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,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_static_path = ["_static"]
12 changes: 7 additions & 5 deletions docs/examples/minimal-class.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from r2t2 import add_reference


@add_reference(short_purpose="Original implementation of R2T2",
reference="Diego Alonso-Álvarez, et al."
"(2018, February 27). Solcore (Version 5.1.0). Zenodo."
"http://doi.org/10.5281/zenodo.1185316")
class MyGreatClass():
@add_reference(
short_purpose="Original implementation of R2T2",
reference="Diego Alonso-Álvarez, et al."
"(2018, February 27). Solcore (Version 5.1.0). Zenodo."
"http://doi.org/10.5281/zenodo.1185316",
)
class MyGreatClass:
pass
13 changes: 7 additions & 6 deletions docs/examples/minimal-method.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from r2t2 import add_reference


class MyGreatClass():

@add_reference(short_purpose="Original implementation of R2T2",
reference="Diego Alonso-Álvarez, et al."
"(2018, February 27). Solcore (Version 5.1.0). Zenodo."
"http://doi.org/10.5281/zenodo.1185316")
class MyGreatClass:
@add_reference(
short_purpose="Original implementation of R2T2",
reference="Diego Alonso-Álvarez, et al."
"(2018, February 27). Solcore (Version 5.1.0). Zenodo."
"http://doi.org/10.5281/zenodo.1185316",
)
def my_great_function(self):
pass
10 changes: 6 additions & 4 deletions docs/examples/minimal.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from r2t2 import add_reference


@add_reference(short_purpose="Original implementation of R2T2",
reference="Diego Alonso-Álvarez, et al."
"(2018, February 27). Solcore (Version 5.1.0). Zenodo."
"http://doi.org/10.5281/zenodo.1185316")
@add_reference(
short_purpose="Original implementation of R2T2",
reference="Diego Alonso-Álvarez, et al."
"(2018, February 27). Solcore (Version 5.1.0). Zenodo."
"http://doi.org/10.5281/zenodo.1185316",
)
def my_great_function():
pass

Expand Down
33 changes: 13 additions & 20 deletions r2t2/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from .writers import REGISTERED_WRITERS
from .docstring_reference_parser import (
expand_file_list,
parse_and_add_docstring_references_from_files
parse_and_add_docstring_references_from_files,
)


Expand Down Expand Up @@ -38,7 +38,7 @@ def add_common_arguments(parser: argparse.ArgumentParser):
default="terminal",
type=str,
choices=sorted(REGISTERED_WRITERS.keys()),
help="Format of the output. Default: Terminal."
help="Format of the output. Default: Terminal.",
)
parser.add_argument(
"--encoding",
Expand All @@ -53,11 +53,7 @@ def add_common_arguments(parser: argparse.ArgumentParser):
help="File to save the references into. Ignored if format is 'Terminal'."
" Default: [target folder]/references.",
)
parser.add_argument(
"--debug",
action="store_true",
help="Enable debug logging"
)
parser.add_argument("--debug", action="store_true", help="Enable debug logging")


class RunSubCommand(SubCommand):
Expand Down Expand Up @@ -102,27 +98,24 @@ def add_arguments(self, parser: argparse.ArgumentParser):
"target",
default=".",
type=str,
help="Target file or folder to analyse."
" Default: Current directory.",
help="Target file or folder to analyse." " Default: Current directory.",
)

def run(self, args: argparse.Namespace):
if args.notebook:
if not args.target.endswith('.ipynb'):
raise Exception("If --notebook flag is passed, target must be a"
" Jupyter notebook!")
if not args.target.endswith(".ipynb"):
raise Exception(
"If --notebook flag is passed, target must be a"
" Jupyter notebook!"
)
locate_references(args.target, encoding=args.encoding)
if args.docstring or args.notebook:
parse_and_add_docstring_references_from_files(
expand_file_list(args.target),
encoding=args.encoding
expand_file_list(args.target), encoding=args.encoding
)


SUB_COMMANDS: List[SubCommand] = [
RunSubCommand(),
StaticSubCommand()
]
SUB_COMMANDS: List[SubCommand] = [RunSubCommand(), StaticSubCommand()]

SUB_COMMAND_BY_NAME: Dict[str, SubCommand] = {
sub_command.name: sub_command for sub_command in SUB_COMMANDS
Expand Down Expand Up @@ -169,6 +162,6 @@ def main(argv: List[str] = None):
run(args)


if __name__ == '__main__':
logging.basicConfig(level='INFO')
if __name__ == "__main__":
logging.basicConfig(level="INFO")
main()
7 changes: 6 additions & 1 deletion r2t2/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class FunctionReference(NamedTuple):
name: str
line: int
source: str
package: str
short_purpose: List[str]
references: List[str]

Expand Down Expand Up @@ -116,13 +117,17 @@ def wrapper(wrapped, instance, args, kwargs):
source = inspect.getsourcefile(wrapped)
line = inspect.getsourcelines(wrapped)[1]
identifier = f"{source}:{line}"
try:
package = inspect.getmodule(inspect.stack()[1][0]).__name__.split(".")[0]
except AttributeError:
package = ""

if identifier in BIBLIOGRAPHY and ref in BIBLIOGRAPHY[identifier].references:
return wrapped(*args, **kwargs)

if identifier not in BIBLIOGRAPHY:
BIBLIOGRAPHY[identifier] = FunctionReference(
wrapped.__name__, line, source, [], []
wrapped.__name__, line, source, package, [], []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: with that many arguments it might be good to use keyword arguments?

)

BIBLIOGRAPHY[identifier].short_purpose.append(short_purpose)
Expand Down
34 changes: 17 additions & 17 deletions r2t2/docstring_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,51 +13,53 @@
"""


DEFAULT_ENCODING = 'utf-8'
DEFAULT_ENCODING = "utf-8"


class CodeDocumentComment(NamedTuple):
text: str
filename: Optional[str] = None
lineno: Optional[int] = None
name: Optional[str] = None
package: Optional[str] = None


def iter_extract_docstring_from_text(
text: str, filename: str = None,
text: str,
filename: str = None,
notebook: bool = False,
) -> Iterable[CodeDocumentComment]:
tree = ast.parse(text, filename=filename or '<unknown>')
tree = ast.parse(text, filename=filename or "<unknown>")
for node in ast.walk(tree):
LOGGER.debug('node: %r', node)
LOGGER.debug("node: %r", node)
try:
node_docstring = ast.get_docstring(node)
LOGGER.debug('node_docstring: %r', node_docstring)
LOGGER.debug("node_docstring: %r", node_docstring)
if node_docstring:
if notebook:
lineno = 'n/a'
lineno = "n/a"
else:
lineno = getattr(node, 'lineno', 1)
lineno = getattr(node, "lineno", 1)
yield CodeDocumentComment(
filename=filename,
lineno=lineno,
name=getattr(node, 'name', None),
text=node_docstring
name=getattr(node, "name", None),
text=node_docstring,
package="",
)
except TypeError:
# node type may not be able to have docstrings
pass


def iter_extract_docstring_from_lines(
lines: Iterable[str]
lines: Iterable[str],
) -> Iterable[CodeDocumentComment]:
return iter_extract_docstring_from_text('\n'.join(lines))
return iter_extract_docstring_from_text("\n".join(lines))


def iter_extract_docstring_from_file(
path: Union[str, Path],
encoding: str = DEFAULT_ENCODING
path: Union[str, Path], encoding: str = DEFAULT_ENCODING
) -> Iterable[CodeDocumentComment]:
path = Path(path)
txt = path.read_text(encoding=encoding)
Expand All @@ -72,13 +74,11 @@ def iter_extract_docstring_from_file(
txt.append(FAKE_FUNC.format(i, " ".join(c["source"])))
txt = "\n".join(txt)
notebook = True
return iter_extract_docstring_from_text(txt, filename=str(path),
notebook=notebook)
return iter_extract_docstring_from_text(txt, filename=str(path), notebook=notebook)


def iter_extract_docstring_from_files(
paths: Iterable[Union[str, Path]],
**kwargs
paths: Iterable[Union[str, Path]], **kwargs
) -> Iterable[CodeDocumentComment]:
for path in paths:
yield from iter_extract_docstring_from_file(path, **kwargs)
27 changes: 11 additions & 16 deletions r2t2/docstring_reference_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@

from r2t2.core import Biblio, BIBLIOGRAPHY, FunctionReference
from r2t2.plain_text_parser import iter_parse_plain_text_references
from r2t2.docstring_parser import (
CodeDocumentComment,
iter_extract_docstring_from_files
)
from r2t2.docstring_parser import CodeDocumentComment, iter_extract_docstring_from_files


LOGGER = logging.getLogger(__name__)


DOCSTRING_SHORT_PURPOSE = 'automatically parsed from docstring'
NOTEBOOK_SHORT_PURPOSE = 'automatically parsed from markdown cell'
DOCSTRING_SHORT_PURPOSE = "automatically parsed from docstring"
NOTEBOOK_SHORT_PURPOSE = "automatically parsed from markdown cell"


def expand_file_list(path: Union[Path, str]) -> List[Path]:
Expand All @@ -34,25 +31,25 @@ def get_function_reference_identifier(function_reference: FunctionReference) ->


def get_function_reference_from_docstring(
docstring: CodeDocumentComment
docstring: CodeDocumentComment,
) -> FunctionReference:
references = list(iter_parse_plain_text_references(docstring.text))
if docstring.lineno != 'n/a':
if docstring.lineno != "n/a":
purpose = DOCSTRING_SHORT_PURPOSE
else:
purpose = NOTEBOOK_SHORT_PURPOSE
return FunctionReference(
source=docstring.filename or '',
source=docstring.filename or "",
line=docstring.lineno or 0,
name=docstring.name or '',
package=docstring.package or "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: perhaps we should allow package to be None? (see #67 (comment))
To be honest, the only reason why I added those or "" was because it wasn't marked as optional in FunctionReference and I just wanted to get passed the linter.

name=docstring.name or "",
references=references,
short_purpose=[purpose] * len(references)
short_purpose=[purpose] * len(references),
)


def iter_parse_docstring_function_references_from_files(
filenames: Iterable[Union[str, Path]],
**kwargs
filenames: Iterable[Union[str, Path]], **kwargs
) -> Iterable[Tuple[str, FunctionReference]]:
for docstring in iter_extract_docstring_from_files(filenames, **kwargs):
function_reference = get_function_reference_from_docstring(docstring)
Expand All @@ -62,9 +59,7 @@ def iter_parse_docstring_function_references_from_files(


def parse_and_add_docstring_references_from_files(
filenames: Iterable[Union[str, Path]],
biblio: Biblio = None,
**kwargs
filenames: Iterable[Union[str, Path]], biblio: Biblio = None, **kwargs
):
if biblio is None:
biblio = BIBLIOGRAPHY
Expand Down
12 changes: 6 additions & 6 deletions r2t2/plain_text_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@
from typing import Iterable, List


DOI_URL_HTTPS_PREFIX = 'https://doi.org/'
DOI_URL_HTTPS_PREFIX = "https://doi.org/"


def iter_doi(text: str) -> Iterable[str]:
for m in re.findall(r'\b10\.\d{4,}/\S+', text):
for m in re.findall(r"\b10\.\d{4,}/\S+", text):
yield DOI_URL_HTTPS_PREFIX + str(m)


def iter_sphinx_reference_names(text: str) -> Iterable[str]:
for m in re.finditer(r':cite:`([^`]+)`', text):
for m in re.finditer(r":cite:`([^`]+)`", text):
yield m.group(1)


def iter_latex_reference_names(text: str) -> Iterable[str]:
for m in re.finditer(r'\\cite(?:\[[^\]]*\])?{([^}]+)}', text):
for m in re.finditer(r"\\cite(?:\[[^\]]*\])?{([^}]+)}", text):
yield m.group(1)


def iter_doxygen_reference_names(text: str) -> Iterable[str]:
for m in re.finditer(r'\\cite\s(\S+)', text):
for m in re.finditer(r"\\cite\s(\S+)", text):
yield m.group(1)


Expand All @@ -33,7 +33,7 @@ def iter_parse_plain_text_raw_bib_references(text: str) -> Iterable[str]:

def iter_parse_plain_text_bib_references(text: str) -> Iterable[str]:
for raw_reference in iter_parse_plain_text_raw_bib_references(text):
for ref_name in raw_reference.split(','):
for ref_name in raw_reference.split(","):
yield ref_name.strip()


Expand Down
Loading