diff --git a/.coveragerc b/.coveragerc index 2f56261..6139a8b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,7 +1,7 @@ # .coveragerc to control coverage.py [run] branch = True -source = lib +source = equator/lib [report] # Regexes for lines to exclude from consideration diff --git a/.gitignore b/.gitignore index 1d1cfcf..edaf226 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,145 @@ - +# Byte-compiled / optimized / DLL files __pycache__/ -.venv/ -.venv_win/ -.venv_linux/ -.pytest_cache/ +*.py[cod] +*$py.class -.coverage +# 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/ .coverage_html/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +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/ + +# 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 +.venv_linux/ +.venv_win/ +.venv_windows/ +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/ +# Temporary files temp.* diff --git a/.vscode/launch.json b/.vscode/launch.json index 3314e75..655fdc4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -19,18 +19,10 @@ "module": "pytest" }, { - "name": "Equator: Run Interpreter (Windows)", + "name": "Equator: Run Interpreter", "type": "python", "request": "launch", - "program": "${workspaceFolder}\\equator.py", - "cwd": "${workspaceFolder}", - "console": "integratedTerminal" - }, - { - "name": "Equator: Run Interpreter (Linux)", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/equator.py", + "module": "equator", "cwd": "${workspaceFolder}", "console": "integratedTerminal" } diff --git a/README.md b/README.md index 7da8eeb..18e017c 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,25 @@ A maths interpreter built in Python using SymPy * Simplify expressions * It can do maths faster than you +# Installation + +Equator can be installed from PyPi. Run the command +``` +pip3 install equatorpy +``` + # Usage: ## Start-up: -Run the file `equator.py` in the top-level directory. For example, `python3 equator.py`. The program has been tested on Windows and Linux (Ubuntu), but not MacOS. It might work, but it might not. +Equator can be run directly through the Python module. Run the command +``` +python3 -m equator +``` +Alternatively, Equator can be invoked directly using the command +``` +equator +``` +The program has been tested on Windows and Linux (Ubuntu), but not MacOS. It might work, but it might not. * If no arguments are provided, the program launches into a full interpreter, built using the Curses library. * If it is started with the argument `json`, then its output will be in a JSON format, where each line of input is treated as one set of equations and expressions. Each input will have one line of respective output, in [JSON format](https://github.com/MiguelGuthridge/Equator/wiki/JSON-Format-Specification). * If it is started with the argument `ev`, then it will run a quick evaluation on the following argument. For example `equator ev "1 + 1"` would print `2`. @@ -21,4 +36,21 @@ Run the file `equator.py` in the top-level directory. For example, `python3 equa * Use standard mathematical syntax (+, -, *, /, ^, =, etc) * Functional notation is used for unary operators: * `|A|` should be written as `abs(A)` -* Equations or expressions are seperated by semicolons +* Equations or expressions are separated by semicolons + +# Development + +When working with Equator, it is recommended to work with a virtual environment. + +After `git clone`ing the repository, set up a virtual environment using [these +instructions](https://docs.python.org/3/library/venv.html), then install the +required dependencies using the command +``` +pip3 install -r requirements.txt +``` +or if you're on Windows +``` +pip install -r requirements_windows.txt +``` + +You should then be able to debug or develop the interpreter normally. diff --git a/__init__.py b/__init__.py deleted file mode 100644 index b3af61a..0000000 --- a/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# For when importing Equator as a module - -from .lib import equate, Expression -from .lib.tokens import Token, Operator, Number, Constant, Symbol, BadToken -from .lib.eq_except import * diff --git a/equator/__init__.py b/equator/__init__.py new file mode 100644 index 0000000..c472c2f --- /dev/null +++ b/equator/__init__.py @@ -0,0 +1,14 @@ +"""Equator + +An advanced symbolic calculator built using SymPy + +This project is licensed under the GNU General Public License v3.0 + +Author: Miguel Guthridge +""" + +from .lib import equate, Expression +from .lib.tokens import Token, Operator, Number, Constant, Symbol, BadToken +from .lib.eq_except import * + +from .equator import main diff --git a/equator/__main__.py b/equator/__main__.py new file mode 100644 index 0000000..2cffa9e --- /dev/null +++ b/equator/__main__.py @@ -0,0 +1,5 @@ + +from .equator import main + +if __name__ == "__main__": + exit(main()) diff --git a/eq_curses/__init__.py b/equator/eq_curses/__init__.py similarity index 100% rename from eq_curses/__init__.py rename to equator/eq_curses/__init__.py diff --git a/eq_curses/colours.py b/equator/eq_curses/colours.py similarity index 100% rename from eq_curses/colours.py rename to equator/eq_curses/colours.py diff --git a/eq_curses/curses_main.py b/equator/eq_curses/curses_main.py similarity index 99% rename from eq_curses/curses_main.py rename to equator/eq_curses/curses_main.py index 9c8d0d8..18f1965 100644 --- a/eq_curses/curses_main.py +++ b/equator/eq_curses/curses_main.py @@ -13,8 +13,8 @@ import curses -from lib import consts -from lib import Expression +from equator.lib import consts +from equator import Expression from .output_container import OutputContainer from . import display_exp, colours diff --git a/eq_curses/display_exp.py b/equator/eq_curses/display_exp.py similarity index 89% rename from eq_curses/display_exp.py rename to equator/eq_curses/display_exp.py index 1f0f594..0f86b92 100644 --- a/eq_curses/display_exp.py +++ b/equator/eq_curses/display_exp.py @@ -3,10 +3,8 @@ import curses -from lib import tokens - -from lib.tokens import Token -from lib.expression import Expression +from equator.lib import tokens +from equator import Expression from . import colours @@ -34,7 +32,7 @@ def getColourPair(token: 'tokens.Token') -> int: return curses.color_pair(colours.PROMPT) def displayExpression(row: int, col: int, stdscr: 'curses._CursesWindow', - exp: 'list[Token]', clear:bool=True): + exp: 'list[tokens.Token]', clear:bool=True): # Set to starting row/col stdscr.addstr(row, col, "") @@ -46,12 +44,12 @@ def displayExpression(row: int, col: int, stdscr: 'curses._CursesWindow', if clear: stdscr.clrtoeol() -def unravelInputTokens(tokens: 'list[list[Token]]') -> 'list[Token]': +def unravelInputTokens(tokens: 'list[list[tokens.Token]]') -> 'list[tokens.Token]': ret = [] add_semi = False for t in tokens[0]: if add_semi: - ret.append(Token(";")) + ret.append(tokens.Token(";")) else: add_semi = True ret.extend(t) @@ -80,7 +78,7 @@ def displayInputExpression(row: int, col: int, stdscr: 'curses._CursesWindow', # Clear to end of line stdscr.clrtoeol() -def splitExpression(max_line_len: int, exp: 'list[Token]') -> 'list[list[Token]]': +def splitExpression(max_line_len: int, exp: 'list[tokens.Token]') -> 'list[list[tokens.Token]]': """Split expression so that lines don't go past the end of input """ max_line_len -= 1 diff --git a/eq_curses/input_container.py b/equator/eq_curses/input_container.py similarity index 100% rename from eq_curses/input_container.py rename to equator/eq_curses/input_container.py diff --git a/eq_curses/output_container.py b/equator/eq_curses/output_container.py similarity index 98% rename from eq_curses/output_container.py rename to equator/eq_curses/output_container.py index 312117a..2a23839 100644 --- a/eq_curses/output_container.py +++ b/equator/eq_curses/output_container.py @@ -1,8 +1,9 @@ import curses -from lib.expression import Expression -from lib.eq_except import EqExternalException -from lib.tokens import Token, BadToken + +from equator import Expression +from equator import EqExternalException +from equator import Token, BadToken from .display_exp import displayExpression, displayInputExpression, splitExpression, unravelInputTokens from . import colours diff --git a/eq_json/__init__.py b/equator/eq_json/__init__.py similarity index 100% rename from eq_json/__init__.py rename to equator/eq_json/__init__.py diff --git a/eq_json/json_main.py b/equator/eq_json/json_main.py similarity index 86% rename from eq_json/json_main.py rename to equator/eq_json/json_main.py index 81f4858..ede0403 100644 --- a/eq_json/json_main.py +++ b/equator/eq_json/json_main.py @@ -5,7 +5,7 @@ import json -from lib import equate, EqExternalException +from equator import equate, EqExternalException def json_main(): try: diff --git a/equator.py b/equator/equator.py similarity index 87% rename from equator.py rename to equator/equator.py index 7a3f922..af65fad 100644 --- a/equator.py +++ b/equator/equator.py @@ -8,11 +8,11 @@ """ import sys -from eq_curses import curses_main -from eq_json import json_main -from lib import consts -from lib import Expression -from lib import EqExternalException, EqInternalException +from .eq_curses import curses_main +from .eq_json import json_main +from .lib import consts +from .lib import Expression +from .lib import EqExternalException, EqInternalException def usage(): print("\n".join([ @@ -37,7 +37,8 @@ def quick_equate(eq: list): except EqExternalException as e: print(str(e)) -def main(argv) -> int: +def main() -> int: + argv = sys.argv[1:] try: if len(argv) == 0: curses_main() @@ -63,5 +64,3 @@ def main(argv) -> int: "possible):") if e.input is not None: print(e.input) -if __name__ == "__main__": - exit(main(sys.argv[1:])) diff --git a/lib/__init__.py b/equator/lib/__init__.py similarity index 100% rename from lib/__init__.py rename to equator/lib/__init__.py diff --git a/lib/consts.py b/equator/lib/consts.py similarity index 98% rename from lib/consts.py rename to equator/lib/consts.py index e2d9de7..0f90afc 100644 --- a/lib/consts.py +++ b/equator/lib/consts.py @@ -4,7 +4,7 @@ from enum import Enum NAME = "Equator" -VERSION = "1.0.3" +VERSION = "1.2.0" AUTHOR = "Miguel Guthridge" # Get 15 decimal places of precision - the max given by sympy diff --git a/lib/eq_except.py b/equator/lib/eq_except.py similarity index 100% rename from lib/eq_except.py rename to equator/lib/eq_except.py diff --git a/lib/eq_object.py b/equator/lib/eq_object.py similarity index 100% rename from lib/eq_object.py rename to equator/lib/eq_object.py diff --git a/lib/expression.py b/equator/lib/expression.py similarity index 100% rename from lib/expression.py rename to equator/lib/expression.py diff --git a/lib/main.py b/equator/lib/main.py similarity index 100% rename from lib/main.py rename to equator/lib/main.py diff --git a/lib/operation.py b/equator/lib/operation.py similarity index 100% rename from lib/operation.py rename to equator/lib/operation.py diff --git a/lib/output_formatter.py b/equator/lib/output_formatter.py similarity index 100% rename from lib/output_formatter.py rename to equator/lib/output_formatter.py diff --git a/lib/parsedinput.py b/equator/lib/parsedinput.py similarity index 100% rename from lib/parsedinput.py rename to equator/lib/parsedinput.py diff --git a/lib/segment.py b/equator/lib/segment.py similarity index 100% rename from lib/segment.py rename to equator/lib/segment.py diff --git a/lib/subexpression.py b/equator/lib/subexpression.py similarity index 100% rename from lib/subexpression.py rename to equator/lib/subexpression.py diff --git a/lib/tokens.py b/equator/lib/tokens.py similarity index 100% rename from lib/tokens.py rename to equator/lib/tokens.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9787c3b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/requirements.txt b/requirements.txt index 04d7405..5644c2d 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/requirements_windows.txt b/requirements_windows.txt new file mode 100644 index 0000000..4694cdf --- /dev/null +++ b/requirements_windows.txt @@ -0,0 +1,3 @@ +-r requirements.txt + +windows-curses==2.2.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..2182044 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,48 @@ +[metadata] +name = equatorpy +version = 1.2.0 +author = Miguel Guthridge +author_email = hdsq@outlook.com.au +description = An advanced symbolic calculator and maths interpreter +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/MiguelGuthridge/Equator +project_urls = + Bug Tracker = https://github.com/MiguelGuthridge/Equator/issues +classifiers = + Programming Language :: Python :: 3 + Intended Audience :: Education + License :: OSI Approved :: GNU General Public License v3 (GPLv3) + Operating System :: OS Independent + Development Status :: 5 - Production/Stable + Environment :: Console + Environment :: Console :: Curses +keywords = + equator + calculator + symbolic + math + equation + sympy + json + cli + +[options] +packages = find: +install_requires = + sympy + colorama + windows-curses >= 2.2.0;platform_system=='Windows' +python_requires = >=3.6 + +[options.entry_points] +console_scripts = + equator = equator:main + +[options.packages.find] +where = . +include = + equator + equator.* +exclude = + tests diff --git a/tests/helpers.py b/tests/helpers.py index 5fd51f5..7a75995 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -3,8 +3,8 @@ Author: Miguel Guthridge (hdsq@outlook.com.au) """ -from ..lib import equate -from ..lib import Expression +from equator import equate +from equator import Expression def removeSpacing(s: str) -> str: """Remove spaces from a string""" diff --git a/tests/test_expression_object.py b/tests/test_expression_object.py index c5b23db..59f8235 100644 --- a/tests/test_expression_object.py +++ b/tests/test_expression_object.py @@ -1,9 +1,8 @@ """Ensure that expressions have full code coverage """ -from ..lib import Expression -from ..lib.tokens import Number, Constant, Symbol, Operator -from .helpers import doOneSolutionEq, doManySolutionEq, equate +from equator import Number, Constant, Symbol, Operator +from .helpers import doOneSolutionEq, doManySolutionEq, equate, Expression def test_stringify(): e = Expression("20 + 22") diff --git a/tests/test_invalid_input.py b/tests/test_invalid_input.py index bed733f..3e7c11e 100644 --- a/tests/test_invalid_input.py +++ b/tests/test_invalid_input.py @@ -3,7 +3,7 @@ import pytest -from ..lib.eq_except import * +from equator import * from .helpers import equate diff --git a/tests/test_leading_negative.py b/tests/test_leading_negative.py index c435a65..f803ff3 100644 --- a/tests/test_leading_negative.py +++ b/tests/test_leading_negative.py @@ -4,7 +4,7 @@ """ import pytest from .helpers import doOneSolutionExp, doOneSolutionEq, equate -from ..lib import EqParserException +from equator import EqParserException def test_starting_negative(): assert doOneSolutionExp("-1") == ["-1"]