diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..62ddfec --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,51 @@ +name: Build binaries + +on: + pull_request: + branches: + - master + push: + branches: + - master + +jobs: + linux-elf: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - run: sudo apt-get install libsdl2-2.0-0 + - run: python -m pip install pyinstaller + - run: python -m pip install --find-links ${WHEELS} wxPython + env: + WHEELS: https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04 + - run: python setup.py clean bundle + - uses: actions/upload-artifact@v2 + with: + name: PythonTurtle + path: ${{ github.workspace }}/dist/PythonTurtle + + macos-app: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - run: python -m pip install pyinstaller wxPython + - run: python setup.py clean bundle + - run: rm ${{ github.workspace }}/dist/PythonTurtle + - uses: actions/upload-artifact@v2 + with: + name: PythonTurtle.app + path: ${{ github.workspace }}/dist/ + + windows-exe: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - run: python -m pip install pyinstaller wxPython + - run: python setup.py clean bundle + - uses: actions/upload-artifact@v2 + with: + name: PythonTurtle.exe + path: ${{ github.workspace }}\dist\PythonTurtle.exe diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..4b207e7 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,28 @@ +name: Checks + +on: + pull_request: + branches: + - master + push: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + env: + - flake8 + - pylint + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install prerequisites + run: python -m pip install --upgrade setuptools pip wheel tox + - name: Run ${{ matrix.env }} + run: tox -e ${{ matrix.env }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..875e451 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,29 @@ +name: Publish binaries and Python package + +on: + push: + tags: + - "*" + +jobs: + binaries: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + pypi: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install prerequisites + run: python -m pip install --upgrade pip setuptools twine wheel + - name: Build package + run: python setup.py sdist bdist_wheel + - name: Upload to PyPI + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: twine upload dist/* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..0601295 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,36 @@ +name: Tests + +on: + pull_request: + branches: + - master + push: + branches: + - master + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest + python-version: + - '3.6' + - '3.7' + - '3.8' + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies for Ubuntu + if: matrix.os == 'ubuntu-latest' + run: sudo apt-get install libsdl2-2.0-0 + - name: Install prerequisites + run: python -m pip install --upgrade setuptools pip wheel tox-gh-actions + - name: Run tests + run: tox diff --git a/.gitignore b/.gitignore index 2120f2d..c5c82e7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,11 +6,14 @@ __pycache__/ *.wpu # testing artifacts +/.pytest_cache/ /.tox/ /tests/reports/ # packaging artifacts /build/ /dist/ +/out/ +/PythonTurtle/ /*.egg-info /*.spec diff --git a/.pydevproject b/.pydevproject index 42a154a..56a835d 100644 --- a/.pydevproject +++ b/.pydevproject @@ -2,7 +2,7 @@ -python 2.6 +python 3.6 Default /PythonTurtle diff --git a/.travis.yml b/.travis.yml index 4cfb2b8..0d37109 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,18 @@ --- -dist: xenial -sudo: true +os: linux +dist: bionic language: python python: - 3.6 - 3.7 +- 3.8 install: - pip install tox-travis +- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + sudo apt-get install libsdl2-2.0-0 + ; fi script: - tox @@ -19,20 +23,75 @@ stages: jobs: allow_failures: - - env: TOXENV=pylint - - python: 3.7 + - { os: osx, stage: publish } include: - { stage: lint, python: 3.6, env: TOXENV=flake8 } - { stage: lint, python: 3.6, env: TOXENV=pylint } - - stage: publish + - name: PyPI package + stage: publish + python: 3.6 + install: skip + script: skip deploy: provider: pypi user: coolRR password: - secure: fdbhH+rsqfG9WfILlBZXlNNSXZVHrfBH9zWa7kKiUY55GMYKli+u/MzG1i6Ezn53Vg1wL1NTQMm5rFbbUfBE2l5/+S191saBZGApEhQZhulSkG702FCbpuGs1a26Zm+dhjyiRvuNdnPB8BA61uHSpWFzDgpLurGsZB0TyB2hZqv7LFL1eG56mKh70LTIDO5XmDh5/yDEXfc23M2nS7Zwx2xJEM047Fay/yeZSjo+LEYgCoTM/z9TftyxkyuLOGsiRoBMwL7eb7wKJzk79vSAvgZP/fSuAqc8ut1lrk1S9OIO4I9s7Zpi1QneJSGDJu4IHfV3nlge/+tlN8aeLA6GsSy/RrrzueBdtYTpbt34vO5rVfhJwjHIKTQa4GUF0R6gCiQ0JjJiXLC+UAbePZxzAKQWaxJWWpqq8L9e+LlHWpEtuB4jEqncFs6FeTT7mTWz2HMTQvvIVAsI2xFkItY4yCpE3P5+5f6UgqwqFpuKCXWF6ohQ090wTlNxN5cikNSYdPPQj7hypKzWrYcH5z4Z4pU63mnO2fZgTEZomvYKXa9mcdtZcCBhPNJW9WTgxqKSouPcjYc4me9wk4wv4ncJaCPLlxbuW7T/Nv+Xy/HYn7eljRapnu7/XcVQgB6gG4vxbRkkLkr1Ybj0xcR2neAQcxhBBqzfCyQezh210dPGqF8= - install: skip - script: skip - on: - tags: true + secure: lQ78pXBQXq2hgrgtlgLak15RVzMGpDJP8ncbenJgpdT0EmTc98MRvQeFsNgiwTKLvkyNZzFs1tG2LjdWX5i7DRwxoAXYObzMiexOfTd6tjQt1/KwuZDWpmMqpBmNGRxzpHbX/0gFWlZ6dY5/y5Wh9FSvXfwYzK1dkscgtdOayB3hHR6y/wOodiZa44k49q+dGKGlJnvbaDK6ngescWX+MIwV3LpiWlooB/S6sPbRg991sfuNc+tbO5LVHvlmDy6oECLqZ6uBLHKrSOZMeKULnJKWHFfUBkeb/ajzrP2O3KYD8rvMaeR9a99iQWRcIs97jLNHtcVx1OR79b49A1HnjjuxYdmksSn7sTF0YbugJJbEq0kQLtoNfOY5C+FnSfj8z2CLLAEOKUHgX/JDHoJmwVpnrY1CVLRIR3zR60RchwGO6Y7WqqjQGbpVJN1H+1kY66EXec28kycalbLVprea9YyMkvVNgu4wwiFu5O+2Td3bptqQstSmUZb+fHt334EISFcA3OEn0JEToNPP3ZjmtwJCTNEt+DViZ5gtatsxptGZr5NzJx3BENPyO3jktga1hw9nmY/pjH9Efu8TRdqzGM7WJMgeO6bBlCncwBQHz60r4AOuTFreZ3Cz7DoVDGKBYOk4oOUvbvv1BINAcRhPijSzhh46atakspBKBk0iEn4= + distributions: 'sdist bdist_wheel' + skip_cleanup: true + on: + tags: true + + - name: GNU/Linux AppImage + stage: publish + python: 3.6 + install: + - sudo apt-get install desktop-file-utils + - wget https://raw.githubusercontent.com/AppImage/AppImages/master/pkg2appimage + - chmod 755 pkg2appimage + script: + - ./pkg2appimage AppImage.yml + - bash -c "mv -v out/Python*AppImage PythonTurtle.AppImage" + deploy: + provider: releases + api_key: $GITHUB_TOKEN + file: PythonTurtle.AppImage + skip_cleanup: true + on: + tags: true + + - name: GNU/Linux bundle + stage: publish + python: 3.6 + install: + - pip install PyInstaller + - pip install --find-links https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04 wxPython + script: + - ./setup.py clean bundle + deploy: + provider: releases + api_key: $GITHUB_TOKEN + file: dist/PythonTurtle + skip_cleanup: true + on: + tags: true + + - name: macOS bundle + stage: publish + os: osx + language: generic + install: + - python3 --version + - python3 -m pip install --upgrade pip + - python3 -m pip install PyInstaller wxPython + script: + - ./setup.py clean bundle + deploy: + provider: releases + api_key: $GITHUB_TOKEN + file: dist/PythonTurtle + skip_cleanup: true + on: + tags: true diff --git a/AppImage.yml b/AppImage.yml new file mode 100644 index 0000000..6f2732a --- /dev/null +++ b/AppImage.yml @@ -0,0 +1,31 @@ +app: PythonTurtle + +ingredients: + dist: bionic + sources: + - deb http://archive.ubuntu.com/ubuntu/ bionic main universe + packages: + - python3-minimal + - python3-pip + script: + - python3 -m pip install --find-links https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-16.04 wxPython + - python3 -m pip install setuptools + +script: + - python3 ../../setup.py install --root . + - ln -s /usr/local/bin/PythonTurtle ./usr/bin/ + - cp ../../pythonturtle/resources/turtle.png ./pythonturtle.png + - cat > pythonturtle.desktop < ImportError: libpng12.so.0: cannot open shared object file: No such file or directory + +See https://askubuntu.com/a/978338/14650 + + +## Compatibility Tested with Python version 3.6 and wxPython version 4.0.1. Reported to run on Windows, macOS, Ubuntu Linux, and Fedora. -Development ------------ + +## Development ```bash -git clone https://github.com/cool-RR/PythonTurtle.git +git clone https://github.com/PythonTurtle/PythonTurtle.git cd PythonTurtle python3 -m pythonturtle ``` -Please [open a pull request](https://github.com/cool-RR/PythonTurtle/pulls +Build application bundles like this: + +```bash +python3 setup.py clean bundle +``` + +Please [open a pull request](https://github.com/PythonTurtle/PythonTurtle/pulls ) for contributions or bug fixes. If you can, please also add tests. -About ------ + + +## Citing PythonTurtle + +If you refer to PythonTurtle in academic work, please use this citation format: + +```bibtex +@misc{rachum2009pythonturtle, + author={Rachum, Ram and Bittner, Peter and others}, + title={PythonTurtle: A learning environment for Python suited for beginners and children, inspired by Logo}, + month={may}, + year={2009}, + publisher={GitHub}, + doi={10.5281/zenodo.10465283}, + url={https://github.com/PythonTurtle/PythonTurtle/} +} +``` + + +## License This project is licensed under the MIT license. -PythonTurtle was created by Ram Rachum as a side-project in 2009. I also provide -[freelance Django/Python development services](https://chipmunkdev.com). + + diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..e51cb2b --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,31 @@ +--- +image: Visual Studio 2017 + +environment: + matrix: + - PYTHON: C:\Python36 + +build: off + +install: +- set PATH=C:\Python36\Scripts;%PATH% +- '%PYTHON%\python.exe -m pip install PyInstaller wxPython pypiwin32' + +before_test: +- '%PYTHON%\python.exe -m pip install tox' + +test_script: +- '%PYTHON%\python.exe -m tox -e flake8' +- '%PYTHON%\python.exe -m tox -e pylint' +- '%PYTHON%\python.exe -m tox -e py36' + +after_test: +- '%PYTHON%\python.exe setup.py clean bundle' +- '%PYTHON%\python.exe setup.py bdist_msi' + +artifacts: +- path: dist\PythonTurtle.exe +- path: dist\PythonTurtle-*.msi + +on_success: +- dir dist diff --git a/pythonturtle.wpr b/pythonturtle.wpr index c7eb7eb..63d142b 100644 --- a/pythonturtle.wpr +++ b/pythonturtle.wpr @@ -1,12 +1,13 @@ #!wing -#!version=6.0 +#!version=7.0 ################################################################## -# Wing IDE project file # +# Wing project file # ################################################################## [project attributes] proj.directory-list = [{'dirloc': loc('.'), - 'excludes': (), - 'filter': u'*', + 'excludes': [u'PythonTurtle.egg-info', + u'.tox'], + 'filter': '*', 'include_hidden': False, 'recursive': True, 'watch_for_changes': True}] diff --git a/pythonturtle/__init__.py b/pythonturtle/__init__.py index a3c697f..2046bd5 100644 --- a/pythonturtle/__init__.py +++ b/pythonturtle/__init__.py @@ -10,6 +10,9 @@ basics of Python programming. """ name = 'PythonTurtle' -__version__ = '0.3.0' +__author__ = 'Ram Rachum and contributors' +__author_email__ = 'ram@rachum.com' __license__ = 'MIT' +__source__ = 'https://github.com/PythonTurtle/PythonTurtle' __url__ = 'http://pythonturtle.org' +__version__ = '0.3.2' diff --git a/pythonturtle/__main__.py b/pythonturtle/__main__.py index eeefeb5..ab2e067 100644 --- a/pythonturtle/__main__.py +++ b/pythonturtle/__main__.py @@ -1,5 +1,8 @@ """ Main entry point for executing the PythonTurtle application. + +We need an absolute import here for PyInstaller to work. +See https://github.com/pyinstaller/pyinstaller/issues/2560 """ from pythonturtle import application diff --git a/pythonturtle/application.py b/pythonturtle/application.py index 03b0e60..3eed35e 100644 --- a/pythonturtle/application.py +++ b/pythonturtle/application.py @@ -15,7 +15,7 @@ print("wxPython doesn't seem to be installed. You need to install " "the appropriate prerequisites for your operating system.") print("Please consult the installation instructions in the README at " - "https://github.com/cool-RR/PythonTurtle#installation") + "https://github.com/PythonTurtle/PythonTurtle#installation") import sys sys.exit(255) @@ -220,6 +220,6 @@ def on_about(self, event=None): def run(): multiprocessing.freeze_support() app = wx.App() - ApplicationWindow(None, -1, "PythonTurtle", size=(600, 600)) + ApplicationWindow(None, -1, pythonturtle.name, size=(600, 600)) # import cProfile; cProfile.run("app.MainLoop()") app.MainLoop() diff --git a/pythonturtle/misc/helpers.py b/pythonturtle/misc/helpers.py index 607f7ab..47bad91 100644 --- a/pythonturtle/misc/helpers.py +++ b/pythonturtle/misc/helpers.py @@ -52,7 +52,7 @@ def resource_string(filename): return text.decode() -def log(x): +def log(message): """A very simple logging function""" - print(x) + print(message) sys.stdout.flush() diff --git a/pythonturtle/misc/smartsleep.py b/pythonturtle/misc/smartsleep.py index 3cdb430..3437bf4 100644 --- a/pythonturtle/misc/smartsleep.py +++ b/pythonturtle/misc/smartsleep.py @@ -20,6 +20,7 @@ class Sleeper: def __init__(self, interval): self.interval = interval + self.starting_time = None def __enter__(self, *args, **kwargs): self.starting_time = time.time() diff --git a/pythonturtle/misc/vector.py b/pythonturtle/misc/vector.py index d094336..dedf8cc 100644 --- a/pythonturtle/misc/vector.py +++ b/pythonturtle/misc/vector.py @@ -51,28 +51,23 @@ def __truediv__(self, other): raise VectorError("right hand side is illegal") return Vector(map(lambda x: x / other, self)) - def __rdiv__(self, other): - raise VectorError("you sick pervert! " - "you tried to divide something by a vector!") + @staticmethod + def __rdiv__(): + raise VectorError("you can't divide something by a vector") def __and__(self, other): """ this is a dot product, done like this: a&b - must use () around it because of fucked up operator precedence. + must use () around it because of broken operator precedence. """ if not isinstance(other, Vector): - raise VectorError("trying to do dot product of Vector " - "with non-Vector") - """ - if self.dim()!=other.dim(): - raise("trying to do dot product of Vectors of unequal " - "dimension!") - """ - d = self.dim() - s = 0. - for i in range(d): - s += self[i] * other[i] - return s + raise VectorError("you can't do dot product of Vector with a " + "non-Vector") + dimension = self.dim() + size = 0. + for i in range(dimension): + size += self[i] * other[i] + return size def __rand__(self, other): return self & other @@ -137,15 +132,15 @@ def copy(self): return Vector(self) -def zeros(n): +def zeros(length): """ Returns a zero Vector of length n. """ - return Vector(map(lambda x: 0., range(n))) + return Vector(map(lambda x: 0., range(length))) -def ones(n): +def ones(length): """ Returns a Vector of length n with all ones. """ - return Vector(map(lambda x: 1., range(n))) + return Vector(map(lambda x: 1., range(length))) diff --git a/pythonturtle/shelltoprocess/__init__.py b/pythonturtle/shelltoprocess/__init__.py index 2a8f791..9502531 100644 --- a/pythonturtle/shelltoprocess/__init__.py +++ b/pythonturtle/shelltoprocess/__init__.py @@ -1,6 +1,6 @@ """ This package implements a wxPython shell, based on PyShell, -which controls a seperate Python process, creating with the +which controls a separate Python process, creating with the `multiprocessing` package. Here is the canonical way to use it: diff --git a/pythonturtle/shelltoprocess/console.py b/pythonturtle/shelltoprocess/console.py index cb5ac29..1cf38e6 100644 --- a/pythonturtle/shelltoprocess/console.py +++ b/pythonturtle/shelltoprocess/console.py @@ -42,7 +42,8 @@ def write(self, output): # self.log(output) return self.writefunc(output) - def log(self, output): + @staticmethod + def log(output): print(output) sys.stdout.flush() @@ -118,16 +119,14 @@ def interact(self, banner=None): sys.ps2 except AttributeError: sys.ps2 = "... " - """ - cprt = 'Type "help", "copyright", "credits" or "license" ' \ - 'for more information.' - if banner is None: - self.write("Python %s on %s\n%s\n(%s)\n" % - (sys.version, sys.platform, cprt, - self.__class__.__name__)) - else: - self.write("%s\n" % str(banner)) - """ + # cprt = 'Type "help", "copyright", "credits" or "license" ' \ + # 'for more information.' + # if banner is None: + # self.write("Python %s on %s\n%s\n(%s)\n" % + # (sys.version, sys.platform, cprt, + # self.__class__.__name__)) + # else: + # self.write("%s\n" % str(banner)) more = 0 while True: try: diff --git a/pythonturtle/shelltoprocess/forkedpyshell.py b/pythonturtle/shelltoprocess/forkedpyshell.py index 81a2756..8a08ebb 100644 --- a/pythonturtle/shelltoprocess/forkedpyshell.py +++ b/pythonturtle/shelltoprocess/forkedpyshell.py @@ -774,7 +774,7 @@ def OnShowCompHistory(self): joined = " ".join(his) # sort out only "good" words - newlist = re.split("[ \.\[\]=}(\)\,0-9\"]", joined) + newlist = re.split(r'[ \.\[\]=}(\)\,0-9"]', joined) # length > 1 (mix out "trash") thlist = [] @@ -1033,11 +1033,11 @@ def write(self, text): def fixLineEndings(self, text): """Return text with line endings replaced by OS-specific endings.""" lines = text.split('\r\n') - for l in range(len(lines)): - chunks = lines[l].split('\r') - for c in range(len(chunks)): - chunks[c] = os.linesep.join(chunks[c].split('\n')) - lines[l] = os.linesep.join(chunks) + for line in range(len(lines)): + chunks = lines[line].split('\r') + for chunk in range(len(chunks)): + chunks[chunk] = os.linesep.join(chunks[chunk].split('\n')) + lines[line] = os.linesep.join(chunks) text = os.linesep.join(lines) return text @@ -1558,7 +1558,7 @@ def OnUpdateUI(self, evt): # self.GetData() # if self.textdo.GetTextLength() > 1: # text = self.textdo.GetText() -# # *** Do somethign with the dragged text here... +# # *** Do something with the dragged text here... # self.textdo.SetText('') # else: # filenames = str(self.filename.GetFilenames()) diff --git a/pythonturtle/turtleprocess.py b/pythonturtle/turtleprocess.py index 242fb7e..91a8f70 100644 --- a/pythonturtle/turtleprocess.py +++ b/pythonturtle/turtleprocess.py @@ -7,6 +7,7 @@ import multiprocessing import sys import time +import builtins from . import shelltoprocess from .misc import smartsleep @@ -50,6 +51,9 @@ def send_report(self): def run(self): + builtins.help = builtins.license = builtins.exit = \ + lambda *args, **kwargs: print('Not supported') + self.turtle = Turtle() def go(distance): @@ -194,23 +198,21 @@ def clear(): self.turtle.clear = False self.send_report() - """ - Had trouble implementing `home`. - I couldn't control when the turtle would actually draw a line home. - - def home(): - #\""" - Places the turtle at the center of the screen, facing upwards. - #\""" - old_pen_down = self.turtle.pen_down - pen_up() # Sends a report as well - self.send_report() - self.turtle.pos = Vector((0, 0)) - self.turtle.orientation = 180 - self.send_report() - time.sleep(3) - pen_down(old_pen_down) - """ + # Had trouble implementing `home`. + # I couldn't control when the turtle would actually draw a line home. + + # def home(): + # #\""" + # Places the turtle at the center of the screen, facing upwards. + # #\""" + # old_pen_down = self.turtle.pen_down + # pen_up() # Sends a report as well + # self.send_report() + # self.turtle.pos = Vector((0, 0)) + # self.turtle.orientation = 180 + # self.send_report() + # time.sleep(3) + # pen_down(old_pen_down) def reset(): """ @@ -237,14 +239,12 @@ def reset(): "reset": reset, } - """ - A little thing I tried doing for checking if a color is - valid before setting it to the turtle. Didn't work. - import wx; app=wx.App(); - def valid_color(color): - return not wx.Pen(color).GetColour() == \ - wx.Pen("malformed").GetColour() - """ + # A little thing I tried doing for checking if a color is + # valid before setting it to the turtle. Didn't work. + # import wx; app=wx.App(); + # def valid_color(color): + # return not wx.Pen(color).GetColour() == \ + # wx.Pen("malformed").GetColour() self.console = shelltoprocess.Console(queue_pack=self.queue_pack, locals=locals_for_console) diff --git a/setup.py b/setup.py index b1391e1..9955f00 100755 --- a/setup.py +++ b/setup.py @@ -2,30 +2,123 @@ """ Packaging implementation for PythonTurtle. """ -from os.path import dirname, join +from glob import glob +import os +import os.path +import shutil + +from setuptools import Command from setuptools import setup, find_packages import pythonturtle as package +class SimpleCommand(Command): + """A simple setuptools command (implementation of abstract base class)""" + user_options = [] + + def initialize_options(self): + """Abstract method of the base class (required to be overridden)""" + + def finalize_options(self): + """Abstract method of the base class (required to be overridden)""" + + +class Bundle(SimpleCommand): + """Build an application bundle for the current platform""" + description = __doc__ + + @staticmethod + def run(): + """ + Create an application bundle (using PyInstaller) + """ + import PyInstaller.__main__ # pylint: disable=import-outside-toplevel + + resources_folder = os.path.join('pythonturtle', 'resources') + + def resource_path(file_glob): + return os.path.join(resources_folder, file_glob) + + def include_resources(file_glob): + return '{src}{separator}{dest}'.format( + src=resource_path(file_glob), + separator=os.pathsep, + dest=resources_folder) + + PyInstaller.__main__.run([ + '--name=%s' % package.name, + '--onefile', + '--windowed', + '--add-data=%s' % include_resources('*.ic*'), + '--add-data=%s' % include_resources('*.png'), + '--add-data=%s' % include_resources('*.txt'), + '--icon=%s' % resource_path('icon.ico'), + os.path.join('pythonturtle', '__main__.py'), + ]) + + +class Clean(SimpleCommand): + """Remove build files and folders, including Python byte-code""" + description = __doc__ + + @staticmethod + def run(): + """ + Clean up files not meant for version control + """ + delete_in_root = [ + 'build', + 'dist', + '.eggs', + '*.egg-info', + '.pytest_cache', + '*.spec', + '.tox', + ] + delete_everywhere = [ + '*.pyc', + '__pycache__', + ] + for candidate in delete_in_root: + rmtree_glob(candidate) + for visible_dir in glob('[A-Za-z0-9_]*'): + for candidate in delete_everywhere: + rmtree_glob(os.path.join(visible_dir, '**', candidate)) + + +def rmtree_glob(file_glob): + """Platform independent rmtree, which also allows wildcards (globbing)""" + for item in glob(file_glob, recursive=True): + try: + os.remove(item) + print('%s removed ...' % item) + except OSError: + try: + shutil.rmtree(item) + print('%s/ removed ...' % item) + except OSError as err: + print(err) + + def read_file(filename): """Source the contents of a file""" - with open(join(dirname(__file__), filename)) as file: + with open(os.path.join(os.path.dirname(__file__), filename)) as file: return file.read() setup( name=package.name, version=package.__version__, - license=package.__license__, - author='Ram Rachum', - author_email='ram@rachum.com', + author=package.__author__, + author_email=package.__author_email__, description=package.__doc__.strip().split('\n')[0], + license=package.__license__, long_description_content_type='text/markdown', long_description=read_file('README.md'), url=package.__url__, project_urls={ - 'Source': 'https://github.com/cool-RR/PythonTurtle', + 'Source': package.__source__, }, keywords=['turtle', 'learning', 'children', 'beginners', 'logo'], classifiers=[ @@ -41,6 +134,7 @@ def read_file(filename): 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Education', ], python_requires='>=3', @@ -49,9 +143,14 @@ def read_file(filename): zip_safe=True, # install_requires=['wxPython'], tests_require=['tox'], + test_suite='tests', + cmdclass={ + 'bundle': Bundle, + 'clean': Clean, + }, entry_points={ 'console_scripts': [ - 'PythonTurtle = pythonturtle.__main__:run', + 'PythonTurtle = pythonturtle.__main__:application.run', ], }, ) diff --git a/tests/acceptance/features/module-imports.feature b/tests/acceptance/features/module-imports.feature deleted file mode 100644 index c5e172b..0000000 --- a/tests/acceptance/features/module-imports.feature +++ /dev/null @@ -1,9 +0,0 @@ -Feature: Basic verification of Python version support - As a PythonTurtle developer - I want Python to import modules successfully - So that I can prove that the program would start up - - Scenario: Try to import major modules - Given PythonTurtle is installed - When I import major modules - Then the modules loaded correctly diff --git a/tests/acceptance/steps/given.py b/tests/acceptance/steps/given.py deleted file mode 100644 index 26ade46..0000000 --- a/tests/acceptance/steps/given.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -'Given' step implementations for acceptance tests. Powered by behave. -""" -from behave import given - - -@given(u'PythonTurtle is installed') -def step_impl(context): - """ - Running the test with Tox this should be taken for granted - """ - pass diff --git a/tests/acceptance/steps/then.py b/tests/acceptance/steps/then.py deleted file mode 100644 index 5efd016..0000000 --- a/tests/acceptance/steps/then.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -'Then' step implementations for acceptance tests. Powered by behave. -""" -from behave import then - - -@then(u'the modules loaded correctly') -def step_impl(context): - """ - Do some ultra-basic verifications - """ - [ - pythonturtle, - pythonturtle_misc, - pythonturtle_shelltoprocess - ] = context.imported_modules - - assert pythonturtle.__version__ is not None - assert pythonturtle_misc is not None - assert pythonturtle_shelltoprocess is not None diff --git a/tests/acceptance/steps/when.py b/tests/acceptance/steps/when.py deleted file mode 100644 index d94380a..0000000 --- a/tests/acceptance/steps/when.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -'When' step implementations for acceptance tests. Powered by behave. -""" -from behave import when - - -@when(u'I import major modules') -def step_impl(context): - """ - Try to import PythonTurtle module - """ - import pythonturtle - import pythonturtle.misc - import pythonturtle.shelltoprocess - - context.imported_modules = [ - pythonturtle, - pythonturtle.misc, - pythonturtle.shelltoprocess, - ] diff --git a/tests/unit/test_import.py b/tests/unit/test_import.py new file mode 100644 index 0000000..b98d3dd --- /dev/null +++ b/tests/unit/test_import.py @@ -0,0 +1,57 @@ +""" +Tests for application. +""" + + +def test_import_turtle(): + """ + Try to import PythonTurtle module and perform some stupid verifications. + """ + import pythonturtle + assert pythonturtle.__version__ is not None + + from pythonturtle import application + assert application is not None + + from pythonturtle import helppages + assert helppages is not None + + from pythonturtle import my_turtle + assert my_turtle is not None + + from pythonturtle import turtleprocess + assert turtleprocess is not None + + from pythonturtle import turtlewidget + assert turtlewidget is not None + + +def test_import_misc(): + """ + Try to import the misc submodule and perform some stupid verifications. + """ + from pythonturtle.misc import helpers + assert helpers is not None + + from pythonturtle.misc import smartsleep + assert smartsleep is not None + + from pythonturtle.misc import vector + assert vector is not None + + +def test_import_shelltoprocess(): + """ + Try to import the misc submodule and perform some stupid verifications. + """ + from pythonturtle import shelltoprocess + assert shelltoprocess is not None + + from pythonturtle.shelltoprocess import console + assert console is not None + + from pythonturtle.shelltoprocess import forkedpyshell + assert forkedpyshell is not None + + from pythonturtle.shelltoprocess import shell + assert shell is not None diff --git a/tox.ini b/tox.ini index 83770a8..1679188 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # - Installing wxPython is non-trivial on GNU/Linux. We need to install wheels from a dedicated repository, # located at https://extras.wxpython.org/wxPython4/extras/linux/. For beackground reading see # https://wiki.wxpython.org/How%20to%20install%20wxPython#Installing_wxPython-Phoenix_using_pip -# - The wheel repository specified via --find-links below matches the build environment on Travis CI. +# - The wheel repository specified via --find-links below matches the build environment on GitHub Actions. # - For local Tox runs adapt the wheel repository URLs to match your local environment. [tox] @@ -13,32 +13,37 @@ envlist = pylint py36 py37 + py38 [testenv] -deps = behave +description = Unit tests +deps = + pytest commands = - pip install --find-links https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-16.04 wxPython - behave + {envpython} -m pip install --find-links https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04 wxPython + {envpython} -m pytest [testenv:flake8] -basepython = python3.6 -deps = flake8 -commands = flake8 {posargs} +description = Static code analysis and code style +deps = + flake8 +commands = + {envpython} -m flake8 {posargs} [testenv:pylint] -basepython = python3.6 -deps = pylint +description = Check for errors and code smells +deps = + pylint + pyinstaller commands = - pip install --find-links https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-16.04 wxPython - pylint --rcfile tox.ini {posargs:pythonturtle} + {envpython} -m pip install --find-links https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04 wxPython + {envpython} -m pylint --rcfile tox.ini {posargs:pythonturtle setup} -[behave] -# default_format = progress -junit = yes -junit_directory = tests/reports -paths = tests/acceptance -show_skipped = no -summary = no +[gh-actions] +python = + 3.6: py36 + 3.7: py37 + 3.8: py38 [flake8] exclude = @@ -47,7 +52,16 @@ exclude = .tox build dist + out + PythonTurtle [pylint] -disable = attribute-defined-outside-init,invalid-name,missing-docstring,no-member,self-cls-assignment,too-few-public-methods,too-many-instance-attributes,too-many-locals,too-many-statements,unused-argument -reports = no +disable = attribute-defined-outside-init,invalid-name,missing-docstring,no-member,self-cls-assignment,too-few-public-methods,too-many-instance-attributes,too-many-locals,too-many-statements,unused-argument,fixme,arguments-differ + +[pytest] +addopts = + --color=yes + --ignore=PythonTurtle + --junitxml=tests/reports/unittests.xml + --strict + --verbose