diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5d648e0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{frm,sh,toml,yml}] +indent_size = 2 +indent_style = space + +[*.{py,rst}] +indent_size = 4 +indent_style = space + +[*.md] +trim_trailing_whitespace = false + +[*.bat] +end_of_line = crlf + +[{.pre-commit-config.yaml,pyproject.toml,setup.cfg,tox.ini}] +indent_size = 4 +indent_style = space diff --git a/README.rst b/README.rst index 924ae22..7605d8d 100644 --- a/README.rst +++ b/README.rst @@ -87,24 +87,24 @@ References .. _CHANGELOG: https://github.com/tueda/python-form/blob/master/CHANGELOG.md .. _LICENCE: https://github.com/tueda/python-form/blob/master/LICENCE.md -.. [1] J.A.M. Vermaseren, - New features of FORM, - `arXiv:math-ph/0010025 - `_. -.. [2] J. Kuipers, T. Ueda, J.A.M. Vermaseren and J. Vollinga, - FORM version 4.0, - `Comput.Phys.Commun. 184 (2013) 1453-1467 - `_, - `arXiv:1203.6543 [cs.SC] - `_. -.. [3] https://github.com/vermaseren/form -.. [4] Feng Feng and Rolf Mertig, - FormLink/FeynCalcFormLink : Embedding FORM in Mathematica and FeynCalc, - `arXiv:1212.3522 [hep-ph] - `_. -.. [5] M. Tentyukov and J.A.M. Vermaseren, - Extension of the functionality of the symbolic program FORM by external software, - `Comput.Phys.Commun. 176 (2007) 385-405 - `_, - `arXiv:cs/0604052 - `_. +.. [1] J.A.M. Vermaseren, + New features of FORM, + `arXiv:math-ph/0010025 + `_. +.. [2] J. Kuipers, T. Ueda, J.A.M. Vermaseren and J. Vollinga, + FORM version 4.0, + `Comput.Phys.Commun. 184 (2013) 1453-1467 + `_, + `arXiv:1203.6543 [cs.SC] + `_. +.. [3] https://github.com/vermaseren/form +.. [4] Feng Feng and Rolf Mertig, + FormLink/FeynCalcFormLink : Embedding FORM in Mathematica and FeynCalc, + `arXiv:1212.3522 [hep-ph] + `_. +.. [5] M. Tentyukov and J.A.M. Vermaseren, + Extension of the functionality of the symbolic program FORM by external software, + `Comput.Phys.Commun. 176 (2007) 385-405 + `_, + `arXiv:cs/0604052 + `_. diff --git a/doc/conf.py b/doc/conf.py index 8935aee..079dbd3 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -10,19 +10,20 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath("..")) # Import the package metadata from ../setup.py. def _import_metadata(): - with open('../setup.py') as f: + with open("../setup.py") as f: for line in f.readlines(): - keys = ('name', 'author', 'version') + keys = ("name", "author", "version") for key in keys: - i = line.find(key + '=') + i = line.find(key + "=") if i >= 0: - _metadata[key] = (line[i + len(key) + 1:].rstrip(). - rstrip(',').strip("'")) + _metadata[key] = ( + line[i + len(key) + 1 :].rstrip().rstrip(",").strip("'") + ) _metadata = {} @@ -39,9 +40,9 @@ def _import_metadata(): # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.napoleon', - 'sphinx.ext.viewcode', + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", ] # Add any paths that contain templates here, relative to this directory. @@ -51,40 +52,39 @@ def _import_metadata(): # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = _metadata['name'] -copyright = ('2015-' + str(datetime.date.today().year) + ', ' + - _metadata['author']) -author = _metadata['author'] +project = _metadata["name"] +copyright = "2015-" + str(datetime.date.today().year) + ", " + _metadata["author"] +author = _metadata["author"] # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = _metadata['version'] +version = _metadata["version"] # The full version, including alpha/beta/rc tags. -release = _metadata['version'] +release = _metadata["version"] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = 'en' +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -95,7 +95,7 @@ def _import_metadata(): # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'default' +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -112,7 +112,7 @@ def _import_metadata(): # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'python-formdoc' +htmlhelp_basename = "python-formdoc" # -- Options for LaTeX output --------------------------------------------- @@ -121,15 +121,12 @@ def _import_metadata(): # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -139,8 +136,7 @@ def _import_metadata(): # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'python-form.tex', u'python-form Documentation', - author, 'manual'), + (master_doc, "python-form.tex", u"python-form Documentation", author, "manual"), ] @@ -148,10 +144,7 @@ def _import_metadata(): # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'pyton-form', u'python-form Documentation', - [author], 1) -] +man_pages = [(master_doc, "pyton-form", u"python-form Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -160,9 +153,15 @@ def _import_metadata(): # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'python-form', u'python-form Documentation', - author, 'python-form', ' A Python package for communicating with FORM.', - 'Miscellaneous'), + ( + master_doc, + "python-form", + u"python-form Documentation", + author, + "python-form", + " A Python package for communicating with FORM.", + "Miscellaneous", + ), ] @@ -184,4 +183,4 @@ def _import_metadata(): # epub_uid = '' # A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] +epub_exclude_files = ["search.html"] diff --git a/doc/index.rst b/doc/index.rst index e7c9e5e..8334e2c 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -2,10 +2,10 @@ python-form =========== .. toctree:: - :maxdepth: 4 - :caption: Contents: + :maxdepth: 4 + :caption: Contents: - modules + modules Indices and tables diff --git a/doc/modules.rst b/doc/modules.rst index ab048d1..ac2cf91 100644 --- a/doc/modules.rst +++ b/doc/modules.rst @@ -2,6 +2,6 @@ API Documentation ================= .. toctree:: - :maxdepth: 4 + :maxdepth: 4 - form + form diff --git a/form/datapath.py b/form/datapath.py index fb2a6d0..e42c6bd 100644 --- a/form/datapath.py +++ b/form/datapath.py @@ -12,14 +12,12 @@ def get_data_path(package, resource): # type: (str, str) -> str """Return the full file path of a resource of a package.""" loader = pkgutil.get_loader(package) - if loader is None or not hasattr(loader, 'get_data'): - raise PackageResourceError("Failed to load package: '{0}'".format( - package)) + if loader is None or not hasattr(loader, "get_data"): + raise PackageResourceError("Failed to load package: '{0}'".format(package)) mod = sys.modules.get(package) or loader.load_module(package) - if mod is None or not hasattr(mod, '__file__'): - raise PackageResourceError("Failed to load module: '{0}'".format( - package)) - parts = resource.split('/') + if mod is None or not hasattr(mod, "__file__"): + raise PackageResourceError("Failed to load module: '{0}'".format(package)) + parts = resource.split("/") parts.insert(0, os.path.dirname(mod.__file__)) resource_name = os.path.join(*parts) return resource_name diff --git a/form/formlink.py b/form/formlink.py index f9b6462..d91761a 100644 --- a/form/formlink.py +++ b/form/formlink.py @@ -13,8 +13,18 @@ from .six import string_types if False: - from typing import Any, IO, MutableSequence, Optional, Sequence, Tuple, Union, overload # noqa: E501, F401 + from typing import ( + Any, + IO, + MutableSequence, + Optional, + Sequence, + Tuple, + Union, + overload, + ) # noqa: E501, F401 if True: + def overload(f): # type: ignore # noqa: D103, F811 return None @@ -23,22 +33,22 @@ class FormLink(object): """Connection to a FORM process.""" # The input file for FORM. - _INIT_FRM = get_data_path('form', 'init.frm') + _INIT_FRM = get_data_path("form", "init.frm") # Special keywords for communicating with FORM. - _END_MARK = '__END__' + _END_MARK = "__END__" _END_MARK_LEN = len(_END_MARK) - _PROMPT = '\n__READY__\n' + _PROMPT = "\n__READY__\n" def __init__(self, args=None, keep_log=False): # type: (Optional[Union[str, Sequence[str]]], Union[bool, int]) -> None """Open a connection to a FORM process.""" self._closed = True - self._head = None # type: Optional[str] - self._log = None # type: Optional[MutableSequence[str]] - self._childpid = None # type: Optional[int] - self._formpid = None # type: Optional[int] - self._parentin = None # type: Optional[PushbackReader] + self._head = None # type: Optional[str] + self._log = None # type: Optional[MutableSequence[str]] + self._childpid = None # type: Optional[int] + self._formpid = None # type: Optional[int] + self._parentin = None # type: Optional[PushbackReader] self._parentout = None # type: Optional[IO[str]] self._loggingin = None # type: Optional[PushbackReader] self.open(args, keep_log) @@ -102,12 +112,13 @@ def open(self, args=None, keep_log=False): """ if args is None: - if 'FORM' in os.environ: - args = os.environ['FORM'] + if "FORM" in os.environ: + args = os.environ["FORM"] else: - args = 'form' + args = "form" + if isinstance(args, string_types): - args = shlex.split(args) # Split the arguments. + args = shlex.split(str(args)) # Split the arguments. elif isinstance(args, (list, tuple)): args = list(args) # As a modifiable mutable object. else: @@ -126,9 +137,9 @@ def open(self, args=None, keep_log=False): os.close(fd_childout) os.close(fd_loggingout) - parentin = os.fdopen(fd_parentin, 'r') - parentout = os.fdopen(fd_parentout, 'w') - loggingin = os.fdopen(fd_loggingin, 'r') + parentin = os.fdopen(fd_parentin, "r") + parentout = os.fdopen(fd_parentout, "w") + loggingin = os.fdopen(fd_loggingin, "r") # FORM sends 'pid\n'. s = parentin.readline() @@ -137,23 +148,23 @@ def open(self, args=None, keep_log=False): parentin.close() parentout.close() loggingin.close() - raise IOError('failed to read the first line from FORM') + raise IOError("failed to read the first line from FORM") s = s.rstrip() formpid = int(s) # The parent must send 'pid,ppid\n'. - s = s + ',{0}\n'.format(os.getpid()) + s = s + ",{0}\n".format(os.getpid()) parentout.write(s) parentout.flush() # FORM sends 'OK' (in init.frm). s = parentin.read(2) - if s != 'OK': + if s != "OK": os.waitpid(pid, 0) parentin.close() parentout.close() loggingin.close() - raise IOError('failed to establish the connection to FORM') + raise IOError("failed to establish the connection to FORM") # Change the prompt. - parentout.write('#prompt {0}\n'.format(self._PROMPT.strip())) + parentout.write("#prompt {0}\n".format(self._PROMPT.strip())) # Read the first line of the FORM output. head = loggingin.readline().rstrip() @@ -174,7 +185,7 @@ def open(self, args=None, keep_log=False): else: self._log = None # Turn off the listing of the input. - parentout.write('#-\n') + parentout.write("#-\n") self._childpid = pid self._formpid = formpid self._parentin = PushbackReader(parentin) @@ -192,9 +203,9 @@ def open(self, args=None, keep_log=False): os.close(fd_loggingin) os.dup2(fd_loggingout, sys.__stdout__.fileno()) - args.append('-M') - args.append('-pipe') - args.append('{0},{1}'.format(fd_childin, fd_childout)) + args.append("-M") + args.append("-pipe") + args.append("{0},{1}".format(fd_childin, fd_childout)) args.append(FormLink._INIT_FRM) # In Python 3.2, subprocess.Popen() on UNIX changed the default @@ -204,10 +215,9 @@ def open(self, args=None, keep_log=False): if sys.version_info[0:2] < (3, 2): subprocess.call(args, shell=False) else: - subprocess.call(args, shell=False, - pass_fds=(fd_childin, - fd_childout, - fd_loggingout)) + subprocess.call( + args, shell=False, pass_fds=(fd_childin, fd_childout, fd_loggingout) + ) os.close(fd_childin) os.close(fd_childout) @@ -280,8 +290,10 @@ def wait(timeout): # timeout <= 0 means no wait os.waitpid(self._childpid, 0) else: if wait(max(term, kill)): # either term or kill is 0 - os.kill(self._formpid, - signal.SIGKILL if kill else signal.SIGTERM) + os.kill( + self._formpid, + signal.SIGKILL if kill else signal.SIGTERM, + ) os.waitpid(self._childpid, 0) else: os.waitpid(self._childpid, 0) @@ -306,7 +318,8 @@ def kill(self): # type: () -> None """Kill the FORM process and close the connection.""" self._close(kill=-1) # Kill it immediately. -# self._close(term=-1, kill=1) + + # self._close(term=-1, kill=1) def write(self, script): # type: (str) -> None @@ -317,12 +330,12 @@ def write(self, script): :meth:`flush` or :meth:`read` is called. """ if self._closed: - raise IOError('tried to write to closed connection') + raise IOError("tried to write to closed connection") script = script.strip() if script: assert self._parentout is not None self._parentout.write(script) - self._parentout.write('\n') + self._parentout.write("\n") def flush(self): # type: () -> None @@ -333,7 +346,7 @@ def flush(self): for asynchronous execution of FORM scripts. """ if self._closed: - raise IOError('tried to flush closed connection') + raise IOError("tried to flush closed connection") assert self._parentout is not None self._parentout.flush() @@ -373,12 +386,16 @@ def read(self, name1, name2, name3, name4, name5, name6, name7): # noqa: D102, pass # pragma: no cover @overload # noqa: F811 - def read(self, name1, name2, name3, name4, name5, name6, name7, name8): # noqa: D102, E501 + def read( + self, name1, name2, name3, name4, name5, name6, name7, name8 + ): # noqa: D102, E501 # type: (str, str, str, str, str, str, str, str) -> Tuple[str, str, str, str, str, str, str, str] # noqa: E501 pass # pragma: no cover @overload # noqa: F811 - def read(self, name1, name2, name3, name4, name5, name6, name7, name8, name9): # noqa: D102, E501 + def read( + self, name1, name2, name3, name4, name5, name6, name7, name8, name9 + ): # noqa: D102, E501 # type: (str, str, str, str, str, str, str, str, str) -> Tuple[str, str, str, str, str, str, str, str, str] # noqa: E501 pass # pragma: no cover @@ -435,14 +452,14 @@ def read(self, *names): # type: ignore # noqa: F811 strings. Objects to be read from FORM are expressions, $-variables and preprocessor variables. - ========== ============================= - name meaning - ========== ============================= - "F" expression F - "$x" $-variable $x - "$x[]" factorized $-variable $x - "\`A'" preprocessor variable A - ========== ============================= + ======= ========================= + name meaning + ======= ========================= + "F" expression F + "$x" $-variable $x + "$x[]" factorized $-variable $x + "\`A'" preprocessor variable A + ======= ========================= Note that the communication for the reading is performed within the preprocessor of FORM (i.e., at compile-time), so one may need to write @@ -484,7 +501,7 @@ def read(self, *names): # type: ignore # noqa: F811 """ if self._closed: - raise IOError('tried to read from closed connection') + raise IOError("tried to read from closed connection") if len(names) == 1 and not isinstance(names[0], string_types): names = tuple(names[0]) @@ -501,10 +518,11 @@ def read(self, *names): # type: ignore # noqa: F811 assert self._loggingin is not None for e in names: - if len(e) >= 2 and e[0] == '`' and e[-1] == "'": + if len(e) >= 2 and e[0] == "`" and e[-1] == "'": self._parentout.write( - '#toexternal "{0}{1}"\n'.format(e, self._END_MARK)) - elif len(e) >= 3 and e[0] == '$' and e[-2:] == '[]': + '#toexternal "{0}{1}"\n'.format(e, self._END_MARK) + ) + elif len(e) >= 3 and e[0] == "$" and e[-2:] == "[]": # Special syntax "$x[]" for factorized $-variables. # NOTE: (1) isfactorized($x) is zero when $x is 0 or $x has # only one factor even after FactArg is performed. @@ -514,27 +532,31 @@ def read(self, *names): # type: ignore # noqa: F811 # (3) `$x[1]' is not accessible (segfault) with versions # before Sep 3 2015, if $x has only one factor and # `$x[0]' gives 1. - self._parentout.write(( - "#if `${0}[0]'\n" - "#toexternal \"(%$)\",${0}[1]\n" - "#do i=2,`${0}[0]'\n" - "#toexternal \"*(%$)\",${0}[`i']\n" - "#enddo\n" - "#else\n" - "#if termsin(${0})\n" - "#toexternal \"%$\",${0}\n" - "#else\n" - "#toexternal \"(0)\"\n" - "#endif\n" - "#endif\n" - "#toexternal \"{1}\"\n" - ).format(e[1:-2], self._END_MARK)) - elif len(e) >= 1 and e[0] == '$': self._parentout.write( - '#toexternal "%${1}",{0}\n'.format(e, self._END_MARK)) + ( + "#if `${0}[0]'\n" + '#toexternal "(%$)",${0}[1]\n' + "#do i=2,`${0}[0]'\n" + '#toexternal "*(%$)",${0}[`i\']\n' + "#enddo\n" + "#else\n" + "#if termsin(${0})\n" + '#toexternal "%$",${0}\n' + "#else\n" + '#toexternal "(0)"\n' + "#endif\n" + "#endif\n" + '#toexternal "{1}"\n' + ).format(e[1:-2], self._END_MARK) + ) + elif len(e) >= 1 and e[0] == "$": + self._parentout.write( + '#toexternal "%${1}",{0}\n'.format(e, self._END_MARK) + ) else: self._parentout.write( - '#toexternal "%E{1}",{0}\n'.format(e, self._END_MARK)) + '#toexternal "%E{1}",{0}\n'.format(e, self._END_MARK) + ) self._parentout.write('#redefine FORMLINKLOOPVAR "0"') self._parentout.write(self._PROMPT) self._parentout.flush() @@ -547,35 +569,35 @@ def read(self, *names): # type: ignore # noqa: F811 i = out.find(self._END_MARK, out_start) if i >= 0: result.append(out[:i]) - out = out[i + self._END_MARK_LEN:] + out = out[i + self._END_MARK_LEN :] out_start = 0 break out_start = max(len(out) - self._END_MARK_LEN, 0) - r, _, _ = select.select((self._parentin, self._loggingin), - (), ()) + r, _, _ = select.select((self._parentin, self._loggingin), (), ()) if self._loggingin in r: s = self._loggingin.read() if s: - i = s.rfind('\n') + i = s.rfind("\n") if i >= 0: - msgs = s[:i].split('\n') + msgs = s[:i].split("\n") if self._log is not None: self._log.extend(msgs) for msg in msgs: - if (msg.find('-->') >= 0 or - msg.find('==>') >= 0): + if msg.find("-->") >= 0 or msg.find("==>") >= 0: if self._log: - msg += '\n' - msg += '\n'.join(self._log) + msg += "\n" + msg += "\n".join(self._log) self.close() raise FormError(msg) - self._loggingin.unread(s[i + 1:]) + self._loggingin.unread(s[i + 1 :]) if self._parentin in r: - out += (self._parentin.read() - .replace('\n', '') - .replace('\\', '') - .replace(' ', '')) + out += ( + self._parentin.read() + .replace("\n", "") + .replace("\\", "") + .replace(" ", "") + ) self._parentin.unread(out) @@ -604,14 +626,27 @@ def _dateversion(self): # type: () -> int """Return the build/revision date as an integer "yyyymmdd".""" import re + if self._head: - ma = re.search(r'(?<=\()(.*)(?=\))', self._head) + ma = re.search(r"(?<=\()(.*)(?=\))", self._head) if ma: - s = re.split(r'[, ]+', ma.group(0)) + s = re.split(r"[, ]+", ma.group(0)) if len(s) >= 3: # month - month_names = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec') + month_names = ( + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ) if s[0] in month_names: m = month_names.index(s[0]) + 1 # date @@ -625,7 +660,7 @@ def _dateversion(self): # Return an integer as "yyyymmdd". return y * 10000 + m * 100 + d raise ValueError('failed to parse "{0}"'.format(self._head)) - raise ValueError('no first line') + raise ValueError("no first line") class FormError(RuntimeError): diff --git a/form/ioutil.py b/form/ioutil.py index 0c734c5..54ec0be 100644 --- a/form/ioutil.py +++ b/form/ioutil.py @@ -10,9 +10,7 @@ def set_nonblock(fd): # type: (int) -> None """Set the given file descriptor to non-blocking mode.""" - fcntl.fcntl(fd, - fcntl.F_SETFL, - fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK) + fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK) class PushbackReader(object): @@ -22,7 +20,7 @@ def __init__(self, raw): # type: (IO[str]) -> None """Initialize the reader.""" self._raw = raw - self._buf = '' + self._buf = "" def close(self): # type: () -> None @@ -38,7 +36,7 @@ def read(self): # type: () -> str """Read data from the stream.""" s = self._buf + self._raw.read() - self._buf = '' + self._buf = "" return s def unread(self, s): @@ -58,5 +56,5 @@ def read0(self): the underlying raw stream's ``read()`` occurs. """ s = self._buf - self._buf = '' + self._buf = "" return s diff --git a/scripts/travis.sh b/scripts/travis.sh index cb1547a..7a8eaba 100755 --- a/scripts/travis.sh +++ b/scripts/travis.sh @@ -71,19 +71,22 @@ travis_test_install() { # Get python-form installed. pip install . - # For testing/code coverage. case "$TRAVIS_PYTHON_VERSION" in 2.6) - travis_retry pip install colorama==0.3.9 coverage nose-timer==0.7.0 + travis_retry pip install 'colorama<0.4.0' 'idna<2.8' + travis_retry pip install 'nose-timer<0.7.1' ;; 3.2) - travis_retry pip install coverage==3.7.1 nose-timer + travis_retry pip install 'coverage<4.0.0' 'coveralls<2.0.0' ;; - *) - travis_retry pip install coverage nose-timer + 3.4) + travis_retry pip install 'colorama<0.4.2' + ;; + pypy) + travis_retry pip install 'cryptography<3.3.0' ;; esac - travis_retry pip install coveralls rednose + travis_retry pip install coveralls rednose nose-timer } travis_test_script() { diff --git a/setup.cfg b/setup.cfg index 0f1a430..4b8f655 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,6 +4,12 @@ test=nosetests [bdist_wheel] universal=1 +[flake8] +max-line-length = 88 +extend-ignore = + D202, # for pydocstyle<5 + E203,W503, # for black + [mypy] follow_imports=normal ignore_missing_imports=True diff --git a/setup.py b/setup.py index f247350..7669608 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ def readme(): # type: () -> str """Read the README file.""" - with open('README.rst') as f: + with open("README.rst") as f: return f.read() @@ -14,39 +14,39 @@ def setup_package(): # type: () -> None """Entry point.""" setup( - name='python-form', - version='0.2.3', - description='A package for communicating with FORM', + name="python-form", + version="0.2.3", + description="A package for communicating with FORM", long_description=readme(), - author='Takahiro Ueda', - author_email='tueda@nikhef.nl', - url='https://github.com/tueda/python-form', - license='MIT', + author="Takahiro Ueda", + author_email="tueda@nikhef.nl", + url="https://github.com/tueda/python-form", + license="MIT", classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: MIT License', - 'Operating System :: Unix', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Topic :: Scientific/Engineering :: Mathematics', - 'Topic :: Scientific/Engineering :: Physics', + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Operating System :: Unix", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.2", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Topic :: Scientific/Engineering :: Mathematics", + "Topic :: Scientific/Engineering :: Physics", ], - keywords='binding, form, computer algebra', + keywords="binding, form, computer algebra", packages=find_packages(), - package_data={'form': ['init.frm', 'py.typed']}, - setup_requires=['nose'], + package_data={"form": ["init.frm", "py.typed"]}, + setup_requires=["nose"], ) -if __name__ == '__main__': +if __name__ == "__main__": setup_package() diff --git a/tests/test_form.py b/tests/test_form.py index 38e68fa..4c9518d 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -12,53 +12,68 @@ class FormTestCase(unittest.TestCase): def test_basic(self): """Basic tests for FormLink.""" import re + with form.open() as f: - f.write(''' + f.write( + """ AutoDeclare Vector p; Local F = g_(0,p1,...,p6); trace4,0; .sort - ''') - str_f = f.read('F') - f.write(''' + """ + ) + str_f = f.read("F") + f.write( + """ #$n = termsin_(F); - ''') - str_n = f.read('$n') + """ + ) + str_n = f.read("$n") str_z = f.read("`ZERO_F'") - self.assertEqual(str_f, re.sub(r'\s+', '', ''' + self.assertEqual( + str_f, + re.sub( + r"\s+", + "", + """ 4*p1.p2*p3.p4*p5.p6-4*p1.p2*p3.p5*p4.p6+4*p1.p2*p3.p6*p4.p5 -4*p1.p3*p2.p4*p5.p6+4*p1.p3*p2.p5*p4.p6-4*p1.p3*p2.p6*p4.p5 +4*p1.p4*p2.p3*p5.p6-4*p1.p4*p2.p5*p3.p6+4*p1.p4*p2.p6*p3.p5 -4*p1.p5*p2.p3*p4.p6+4*p1.p5*p2.p4*p3.p6-4*p1.p5*p2.p6*p3.p4 +4*p1.p6*p2.p3*p4.p5-4*p1.p6*p2.p4*p3.p5+4*p1.p6*p2.p5*p3.p4 - ''')) - self.assertEqual(str_n, '15') - self.assertEqual(str_z, '0') + """, + ), + ) + self.assertEqual(str_n, "15") + self.assertEqual(str_z, "0") def test_multiple_read(self): """Test multiple arguments for FormLink.read().""" with form.open() as f: - f.write(''' + f.write( + """ S x; L F1 = (1+x)^2; L F2 = (1-x)^2; .sort - ''') + """ + ) self.assertEqual(f.read(), None) - self.assertEqual(f.read('F1', 'F2'), - ['1+2*x+x^2', '1-2*x+x^2']) + self.assertEqual(f.read("F1", "F2"), ["1+2*x+x^2", "1-2*x+x^2"]) def test_old_style(self): """Tests without the "with" statement.""" f = form.open() try: - f.write(''' + f.write( + """ S x; L F = (1+x)^2; .sort - ''') - self.assertEqual(f.read('F'), '1+2*x+x^2') + """ + ) + self.assertEqual(f.read("F"), "1+2*x+x^2") self.assertEqual(f.closed, False) finally: f.close() @@ -66,60 +81,68 @@ def test_old_style(self): def test_arguments(self): """Test complicated arguments for FormLink.open().""" - with form.open('form -D N1=123 -D N2=456') as f: - self.assertEqual(f.read("`N1'"), '123') - self.assertEqual(f.read("`N2'"), '456') + with form.open("form -D N1=123 -D N2=456") as f: + self.assertEqual(f.read("`N1'"), "123") + self.assertEqual(f.read("`N2'"), "456") - with form.open(('form', '-D', 'N1=123', '-D', 'N2=456')) as f: - self.assertEqual(f.read("`N1'"), '123') - self.assertEqual(f.read("`N2'"), '456') + with form.open(("form", "-D", "N1=123", "-D", "N2=456")) as f: + self.assertEqual(f.read("`N1'"), "123") + self.assertEqual(f.read("`N2'"), "456") def test_flush(self): """Tests for FormLink.flush().""" with form.open() as f: n = 9 m = 10 - f.write(''' + f.write( + """ #define N "{0}" #define M "{1}" S x1,...,x`N'; L F = (x1+...+x`N')^`M'; .sort #do i=1,`N' - id x`i' = 1; + id x`i' = 1; #enddo .sort - '''.format(n, m)) + """.format( + n, m + ) + ) # The following calls of flush() must be irrelevant. f.flush() f.flush() f.flush() f.flush() - self.assertEqual(f.read('F'), str(n ** m)) + self.assertEqual(f.read("F"), str(n ** m)) def test_errors(self): """Tests for erroneous cases.""" # Undeclared symbols. with form.open() as f: - f.write(''' + f.write( + """ L F = (1+x)^2; .sort - ''') - self.assertRaises(FormError, f.read, 'F') + """ + ) + self.assertRaises(FormError, f.read, "F") # Undefined expressions. with form.open() as f: - f.write(''' + f.write( + """ S x; L F = (1+x)^2; .sort - ''') - self.assertRaises(FormError, f.read, 'G') + """ + ) + self.assertRaises(FormError, f.read, "G") # Accesses to closed connections. with form.open() as f: f.close() - self.assertRaises(IOError, f.write, '') + self.assertRaises(IOError, f.write, "") self.assertRaises(IOError, f.flush) self.assertRaises(IOError, f.read) @@ -127,19 +150,23 @@ def test_many_times(self): """Test with many reads/writes.""" with form.open() as f: n = 2000 - f.write(''' + f.write( + """ #$x = 0; - ''') + """ + ) for _ in range(n): - f.write(''' + f.write( + """ #$x = $x + 1; - ''') - f.read('$x') - self.assertEqual(int(f.read('$x')), n) + """ + ) + f.read("$x") + self.assertEqual(int(f.read("$x")), n) def test_keep_log(self): """Tests with keep_log.""" - script = ''' + script = """ On stats; S x; L F = (1+x)^2; @@ -165,97 +192,103 @@ def test_keep_log(self): ;* 9 ;* 10 .sort - ''' + """ with form.open() as f: msg = None f.write(script) try: - f.read('X') + f.read("X") except FormError as e: msg = str(e) self.assertTrue(msg is not None) # Neither F nor G appears in `msg`. - self.assertTrue(msg.find('L F = (1+x)^2;') < 0) - self.assertTrue(msg.find('L G = (1-x)^2;') < 0) + self.assertTrue(msg.find("L F = (1+x)^2;") < 0) + self.assertTrue(msg.find("L G = (1-x)^2;") < 0) with form.open(keep_log=True) as f: msg = None f.write(script) try: - f.read('X') + f.read("X") except FormError as e: msg = str(e) self.assertTrue(msg is not None) # Both F and G appear in `msg`. - self.assertTrue(msg.find('L F = (1+x)^2;') >= 0) - self.assertTrue(msg.find('L G = (1-x)^2;') >= 0) + self.assertTrue(msg.find("L F = (1+x)^2;") >= 0) + self.assertTrue(msg.find("L G = (1-x)^2;") >= 0) with form.open(keep_log=30) as f: msg = None f.write(script) try: - f.read('X') + f.read("X") except FormError as e: msg = str(e) self.assertTrue(msg is not None) # G is still in `msg' but F is not. - self.assertTrue(msg.find('L F = (1+x)^2;') < 0) - self.assertTrue(msg.find('L G = (1-x)^2;') >= 0) + self.assertTrue(msg.find("L F = (1+x)^2;") < 0) + self.assertTrue(msg.find("L G = (1-x)^2;") >= 0) def test_seq_args(self): """Test complicated arguments for FormLink.read().""" with form.open() as f: - f.write(''' + f.write( + """ #do i=1,9 - L F`i' = `i'; + L F`i' = `i'; #enddo .sort - ''') + """ + ) # normal arguments - self.assertEqual(f.read('F1'), '1') - self.assertEqual(f.read('F1', 'F2'), ['1', '2']) - self.assertEqual(f.read('F1', 'F2', 'F3'), ['1', '2', '3']) + self.assertEqual(f.read("F1"), "1") + self.assertEqual(f.read("F1", "F2"), ["1", "2"]) + self.assertEqual(f.read("F1", "F2", "F3"), ["1", "2", "3"]) # a non-string argument - self.assertEqual(f.read(('F1',)), ['1']) - self.assertEqual(f.read(('F1', 'F2')), ['1', '2']) - self.assertEqual(f.read(('F1', 'F2', 'F3')), ['1', '2', '3']) + self.assertEqual(f.read(("F1",)), ["1"]) + self.assertEqual(f.read(("F1", "F2")), ["1", "2"]) + self.assertEqual(f.read(("F1", "F2", "F3")), ["1", "2", "3"]) # a generator - self.assertEqual(f.read('F{0}'.format(i) for i in range(1, 2)), - ['1']) - self.assertEqual(f.read('F{0}'.format(i) for i in range(1, 3)), - ['1', '2']) - self.assertEqual(f.read('F{0}'.format(i) for i in range(1, 4)), - ['1', '2', '3']) + self.assertEqual(f.read("F{0}".format(i) for i in range(1, 2)), ["1"]) + self.assertEqual(f.read("F{0}".format(i) for i in range(1, 3)), ["1", "2"]) + self.assertEqual( + f.read("F{0}".format(i) for i in range(1, 4)), ["1", "2", "3"] + ) # more complicated arguments - self.assertEqual(f.read(['F1'], 'F2'), [['1'], '2']) - self.assertEqual(f.read(['F1'], ['F2']), [['1'], ['2']]) - self.assertEqual(f.read('F1', ['F2', 'F3']), ['1', ['2', '3']]) - self.assertEqual(f.read('F1', ['F2'], ['F3']), ['1', ['2'], ['3']]) - self.assertEqual(f.read(['F1'], ['F2', ['F3', 'F4']]), - [['1'], ['2', ['3', '4']]]) - self.assertEqual(f.read('F1', - (('F{0}').format(i) for i in range(2, 5))), - ['1', ['2', '3', '4']]) + self.assertEqual(f.read(["F1"], "F2"), [["1"], "2"]) + self.assertEqual(f.read(["F1"], ["F2"]), [["1"], ["2"]]) + self.assertEqual(f.read("F1", ["F2", "F3"]), ["1", ["2", "3"]]) + self.assertEqual(f.read("F1", ["F2"], ["F3"]), ["1", ["2"], ["3"]]) + self.assertEqual( + f.read(["F1"], ["F2", ["F3", "F4"]]), [["1"], ["2", ["3", "4"]]] + ) + self.assertEqual( + f.read("F1", (("F{0}").format(i) for i in range(2, 5))), + ["1", ["2", "3", "4"]], + ) def test_long_lines(self): """Test with long lines.""" with form.open() as f: - f.write(''' + f.write( + """ L F = 2^1000; #$x = 2^1000; .sort #define x "`$x'" - ''') + """ + ) answer = str(2 ** 1000) - self.assertEqual(f.read('F'), answer) - self.assertEqual(f.read('$x'), answer) + self.assertEqual(f.read("F"), answer) + self.assertEqual(f.read("$x"), answer) self.assertEqual(f.read("`x'"), answer) def test_empty_lines(self): """Test input with empty lines.""" with form.open() as f: - f.write(''' + f.write( + """ On stats; S x; @@ -264,90 +297,111 @@ def test_empty_lines(self): .sort - ''') - self.assertEqual(f.read('F'), '1+2*x+x^2') + """ + ) + self.assertEqual(f.read("F"), "1+2*x+x^2") def test_factdollar(self): """Tests with factorized $-variables.""" + def join(factors): - return '({0})'.format(')*('.join(factors)) + return "({0})".format(")*(".join(factors)) def check_factors(x, factors): - self.assertEqual(f.read('{0}[0]'.format(x)), str(len(factors))) + self.assertEqual(f.read("{0}[0]".format(x)), str(len(factors))) for i in range(len(factors)): - self.assertEqual(f.read('{0}[{1}]'.format(x, i + 1)), - factors[i]) - self.assertEqual(f.read('{0}[]'.format(x)), join(factors)) + self.assertEqual(f.read("{0}[{1}]".format(x, i + 1)), factors[i]) + self.assertEqual(f.read("{0}[]".format(x)), join(factors)) with form.open() as f: # NOTE: The order of factors was changed by the version # v4.1-20131025-112-g8805b9e [2015-08-31]. - f.write(''' + f.write( + """ S a,b; #$x = (-5)*(a^5-b^5); #factdollar $x - ''') + """ + ) if f._dateversion >= 20150831: - factors = ('b-a', 'b^4+a*b^3+a^2*b^2+a^3*b+a^4', '5') + factors = ("b-a", "b^4+a*b^3+a^2*b^2+a^3*b+a^4", "5") else: - factors = ('5', 'b-a', 'b^4+a*b^3+a^2*b^2+a^3*b+a^4') - self.assertEqual(f.read('$x'), '5*b^5-5*a^5') - check_factors('$x', factors) + factors = ("5", "b-a", "b^4+a*b^3+a^2*b^2+a^3*b+a^4") + self.assertEqual(f.read("$x"), "5*b^5-5*a^5") + check_factors("$x", factors) - f.write(''' + f.write( + """ S a,b; #$x = (a+b)^3; #factdollar $x - ''') - factors = ('b+a', 'b+a', 'b+a') - check_factors('$x', factors) + """ + ) + factors = ("b+a", "b+a", "b+a") + check_factors("$x", factors) - f.write(''' + f.write( + """ #$y = 0; - ''') - self.assertEqual(f.read('$y[]'), '(0)') - f.write(''' + """ + ) + self.assertEqual(f.read("$y[]"), "(0)") + f.write( + """ #factdollar $y - ''') - self.assertEqual(f.read('$y[]'), '(0)') + """ + ) + self.assertEqual(f.read("$y[]"), "(0)") - f.write(''' + f.write( + """ #$z = a; - ''') - self.assertEqual(f.read('$z[]'), 'a') - f.write(''' + """ + ) + self.assertEqual(f.read("$z[]"), "a") + f.write( + """ #factdollar $z - ''') - self.assertEqual(f.read('$z[]'), '(a)') + """ + ) + self.assertEqual(f.read("$z[]"), "(a)") - f.write(''' + f.write( + """ #$w = 2*a; - ''') - self.assertEqual(f.read('$w[]'), '2*a') - f.write(''' + """ + ) + self.assertEqual(f.read("$w[]"), "2*a") + f.write( + """ #factdollar $w - ''') + """ + ) if f._dateversion >= 20150903: - factors = ('a', '2') + factors = ("a", "2") else: - factors = ('2', 'a') - check_factors('$w', factors) + factors = ("2", "a") + check_factors("$w", factors) def test_kill(self): """Tests for FormLink.kill().""" import signal import time + with form.open() as f: + def cb_timeout_handler(signum, frame): - raise RuntimeError('Timeout') + raise RuntimeError("Timeout") def do_test(func): - f.write(''' + f.write( + """ Auto V p; L F = g_(0,p1,...,p30); trace4,0; .sort - ''') + """ + ) f.flush() time.sleep(0.5) @@ -369,26 +423,25 @@ def test_head(self): with form.open() as f: head = f.head self.assertTrue( - head[:4] == 'FORM' or - head[:5] == 'TFORM' or - head[:7] == 'ParFORM' + head[:4] == "FORM" or head[:5] == "TFORM" or head[:7] == "ParFORM" ) def test_environ(self): """Test for $FORM.""" import os - old_form = os.environ.get('FORM', None) + + old_form = os.environ.get("FORM", None) try: - os.environ['FORM'] = 'form -D X=12345' + os.environ["FORM"] = "form -D X=12345" with form.open() as f: - self.assertEqual(f.read("`X'"), '12345') + self.assertEqual(f.read("`X'"), "12345") finally: if old_form is None: - del os.environ['FORM'] + del os.environ["FORM"] else: - os.environ['FORM'] = old_form + os.environ["FORM"] = old_form -if __name__ == '__main__': +if __name__ == "__main__": unittest.main()