Repository: fossasia/knittingpattern Branch: master Commit: e44042988418 Files: 162 Total size: 360.5 KB Directory structure: gitextract_h52otm60/ ├── .codeclimate.yml ├── .gitignore ├── .landscape.yaml ├── .travis.yml ├── CONTRIBUTING.rst ├── DeveloperCertificateOfOrigin.txt ├── LICENSE ├── MANIFEST.in ├── README.rst ├── appveyor.yml ├── dev-requirements.in ├── dev-requirements.txt ├── docs/ │ ├── DevelopmentSetup.rst │ ├── FileFormatSpecification.rst │ ├── Installation.rst │ ├── Makefile │ ├── _static/ │ │ └── .gitignore │ ├── _templates/ │ │ └── .gitignore │ ├── conf.py │ ├── index.rst │ ├── make.bat │ ├── make_html.bat │ ├── reference/ │ │ ├── index.rst │ │ └── knittingpattern/ │ │ ├── Dumper/ │ │ │ ├── FileWrapper.rst │ │ │ ├── file.rst │ │ │ ├── index.rst │ │ │ ├── init.rst │ │ │ ├── json.rst │ │ │ ├── svg.rst │ │ │ └── xml.rst │ │ ├── IdCollection.rst │ │ ├── Instruction.rst │ │ ├── InstructionLibrary.rst │ │ ├── KnittingPattern.rst │ │ ├── KnittingPatternSet.rst │ │ ├── Loader.rst │ │ ├── Mesh.rst │ │ ├── Parser.rst │ │ ├── ParsingSpecification.rst │ │ ├── Prototype.rst │ │ ├── Row.rst │ │ ├── convert/ │ │ │ ├── AYABPNGBuilder.rst │ │ │ ├── AYABPNGDumper.rst │ │ │ ├── InstructionSVGCache.rst │ │ │ ├── InstructionToSVG.rst │ │ │ ├── KnittingPatternToSVG.rst │ │ │ ├── Layout.rst │ │ │ ├── SVGBuilder.rst │ │ │ ├── color.rst │ │ │ ├── image_to_knittingpattern.rst │ │ │ ├── index.rst │ │ │ ├── init.rst │ │ │ └── load_and_dump.rst │ │ ├── index.rst │ │ ├── init.rst │ │ ├── utils.rst │ │ └── walk.rst │ └── test/ │ ├── test_docs.py │ ├── test_documentation_sources_exist.py │ └── test_sphinx_build.py ├── knittingpattern/ │ ├── Dumper/ │ │ ├── FileWrapper.py │ │ ├── __init__.py │ │ ├── file.py │ │ ├── json.py │ │ ├── svg.py │ │ └── xml.py │ ├── IdCollection.py │ ├── Instruction.py │ ├── InstructionLibrary.py │ ├── KnittingPattern.py │ ├── KnittingPatternSet.py │ ├── Loader.py │ ├── Mesh.py │ ├── Parser.py │ ├── ParsingSpecification.py │ ├── Prototype.py │ ├── Row.py │ ├── __init__.py │ ├── convert/ │ │ ├── AYABPNGBuilder.py │ │ ├── AYABPNGDumper.py │ │ ├── InstructionSVGCache.py │ │ ├── InstructionToSVG.py │ │ ├── KnittingPatternToSVG.py │ │ ├── Layout.py │ │ ├── SVGBuilder.py │ │ ├── __init__.py │ │ ├── color.py │ │ ├── image_to_knittingpattern.py │ │ ├── load_and_dump.py │ │ └── test/ │ │ ├── pictures/ │ │ │ └── conversion.tif │ │ ├── test_AYABPNGBuilder.py │ │ ├── test_SVGBuilder.py │ │ ├── test_convert.py │ │ ├── test_default_instruction_layout.py │ │ ├── test_default_svgs.py │ │ ├── test_images.py │ │ ├── test_instruction_to_svg.py │ │ ├── test_knittingpattern_to_png.py │ │ ├── test_layout.py │ │ ├── test_patterns/ │ │ │ ├── add and remove meshes.json │ │ │ ├── block4x4.json │ │ │ ├── cast_on_and_bind_off.json │ │ │ ├── small-cafe.json │ │ │ ├── split_up_and_add_rows.json │ │ │ └── with hole.json │ │ ├── test_png_to_knittingpattern.py │ │ └── test_save_as_svg.py │ ├── examples/ │ │ ├── Cafe.json │ │ ├── Charlotte.json │ │ ├── README.md │ │ ├── all-instructions.json │ │ ├── block4x4.json │ │ ├── empty.json │ │ ├── negative-rendering.json │ │ └── new-knitting-pattern.py │ ├── instructions/ │ │ ├── bo.json │ │ ├── cdd.json │ │ ├── co.json │ │ ├── k2tog.json │ │ ├── knit.json │ │ ├── purl.json │ │ ├── skp.json │ │ └── yo.json │ ├── test/ │ │ ├── conftest.py │ │ ├── pattern/ │ │ │ ├── inheritance.json │ │ │ ├── row_mapping_pattern.json │ │ │ ├── row_removal_pattern.json │ │ │ └── single_instruction.json │ │ ├── test_add_and_remove_instructions.py │ │ ├── test_change_row_mapping.py │ │ ├── test_default_instructions.py │ │ ├── test_dump_json.py │ │ ├── test_dumper.py │ │ ├── test_example_code.py │ │ ├── test_example_rows.py │ │ ├── test_examples.py │ │ ├── test_id_collection.py │ │ ├── test_instruction.py │ │ ├── test_instruction_library.py │ │ ├── test_instruction_row_inheritance.py │ │ ├── test_instructions/ │ │ │ ├── recursion/ │ │ │ │ ├── test_instruction_3.json │ │ │ │ └── test_instruction_4.json │ │ │ ├── test_instruction_1.json │ │ │ └── test_instruction_2.json │ │ ├── test_knittingpattern.py │ │ ├── test_load_instructions.py │ │ ├── test_loader.py │ │ ├── test_parsing.py │ │ ├── test_row_instructions.py │ │ ├── test_row_mapping.py │ │ ├── test_row_meshes.py │ │ ├── test_utilities.py │ │ └── test_walk.py │ ├── utils.py │ └── walk.py ├── pylintrc ├── requirements.in ├── requirements.txt ├── setup.cfg ├── setup.py ├── test-requirements.in └── test-requirements.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .codeclimate.yml ================================================ engines: duplication: enabled: true config: languages: - python fixme: enabled: true radon: enabled: true pep8: enabled: true radon: enabled: true ratings: paths: - "**.py" exclude_paths: [] ================================================ FILE: .gitignore ================================================ /.cache /*.egg-info /*.__pycache__ /dist /build *.pyc *.coverage /.eggs *.swp /.idea ================================================ FILE: .landscape.yaml ================================================ # https://docs.landscape.io/configuration.html strictness: veryhigh python-targets: - 3 pep8: full: true doc-warnings: yes test-warnings: yes max-line-length: 79 autodetect: yes ================================================ FILE: .travis.yml ================================================ language: python cache: pip sudo: required python: - '3.3' - '3.4' - '3.5' install: # install the package - PACKAGE_VERSION=`python setup.py --version` - TAG_NAME=v$PACKAGE_VERSION # install from the zip file to see if files were forgotten - python setup.py sdist --dist-dir=dist --formats=zip - ( cd dist ; pip install knittingpattern-${PACKAGE_VERSION}.zip ) # install the test requirements - pip install -r test-requirements.txt before_script: # remove the build folder because it creates problems for py.test - rm -rf build # show the versions - python setup.py --version - py.test --version - python setup.py requirements - echo Package version $PACKAGE_VERSION with possible tag name $TAG_NAME script: # test with pep8 # add coverage for python 3.3 and above - py.test --cov=knittingpattern --pep8 # test import form everywhere - ( cd / && python -c "import knittingpattern;print(\"imported\")" ) # run tests from installation - "( cd / && py.test --pyargs knittingpattern )" # test that the tag represents the version # https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables - ( if [ -n "$TRAVIS_TAG" ]; then if [ $TAG_NAME != $TRAVIS_TAG ]; then echo "This tag is for the wrong version. Got \"$TRAVIS_TAG\" expected \"$TAG_NAME\"."; exit 1; fi; fi; ) after_script: # https://github.com/codeclimate/python-test-reporter # set the environment variable CODECLIMATE_REPO_TOKEN in travis-ci settings - codeclimate-test-reporter before_deploy: # create the documentation - ( cd docs ; make html ) - pip install wheel - python setup.py bdist_wheel deploy: # created with travis command line tool # https://docs.travis-ci.com/user/deployment/pypi # $ travis setup pypi provider: pypi user: niccokunzmann2 password: secure: nqxnwagFphdLLJsjt4of/jMnLtsMtk8HqoPmENodEfaeue0A4ziIIm46tSBKdBqHURxuCFjj8siuaVCsXiZso/b4aJaIy08C3dcYltiLTfnYDJisicijEMg2wLNffJqiTN2+W6trHFJ7TzYz932jQEMmOC09mynt7LbP7RJOfqOGxvHiVL8D7I677xNLz+Kgu5R5FfZ9lzWcuBEbFTLffFcITeqVM+0yGJv9pZ+rud3RXl3qCAFYsG7SlHnzGuOyV/vWdAmfEuCvW+bs3oFS85Im4LiD1YTE5CQZhrwwGslncjOlWOAlrMuJzWmAG/6OTrIK7nIpI5gVlZdkesZQsx6JeR/22rVjkD9UcKj1R+7lzsC2X9Lh+vMRtxHJnDlW7clUA9+qw+TnvmR85UUhnmaaGtGJwZXDi0TP9wYmg3TaxoKKx5SnYDyFIq5kbVnSxSu1ng0qFMszGH1HYR350fEk8/so3IxdAbrHYbK5xeMv0vISJXdzIv/0U14lb4uB3agWf+SANQkrjYNx4BSE5zP1qj2HC2NsGwXdkl/8HjbTFe9Daj5nLTmGRL80GQ0BpRyJrT5wERRqozuWM1Jb1v6kgADhZhWAUryFlTZ+875He1CYXXoVSI59IER1ccK89NautsrF3mW/4o/WXCTzPDHtDkdavAvVPJc34oTmZgI= on: tags: true distributions: sdist bdist_wheel repo: fossasia/knittingpattern ================================================ FILE: CONTRIBUTING.rst ================================================ How to Contribute ================= 1. Read and agree to the `Developer Certificate of Origin `_. ================================================ FILE: DeveloperCertificateOfOrigin.txt ================================================ Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 660 York Street, Suite 102, San Francisco, CA 94110 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ================================================ FILE: LICENSE ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright © 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License. “The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”. The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: MANIFEST.in ================================================ recursive-include knittingpattern *.py *.json *.svg include *requirements.txt include LICENSE include knittingpattern/convert/test/pictures/* ================================================ FILE: README.rst ================================================ Knitting Pattern Library =============== The knitting pattern library enables tailors, artisans and home knitters to use a common pattern for knitting machines and hand made knits. .. image:: https://travis-ci.org/fossasia/knittingpattern.svg :target: https://travis-ci.org/fossasia/knittingpattern :alt: Build Status .. image:: https://ci.appveyor.com/api/projects/status/c1983ovsc8thlhvi?svg=true :target: https://ci.appveyor.com/project/AllYarnsAreBeautiful/knittingpattern :alt: AppVeyor CI build status (Windows) .. image:: https://codeclimate.com/github/fossasia/knittingpattern/badges/gpa.svg :target: https://codeclimate.com/github/fossasia/knittingpattern :alt: Code Climate .. image:: https://codeclimate.com/github/fossasia/knittingpattern/badges/coverage.svg :target: https://codeclimate.com/github/fossasia/knittingpattern/coverage :alt: Test Coverage .. image:: https://codeclimate.com/github/fossasia/knittingpattern/badges/issue_count.svg :target: https://codeclimate.com/github/fossasia/knittingpattern :alt: Issue Count .. image:: https://badge.fury.io/py/knittingpattern.svg :target: https://pypi.python.org/pypi/knittingpattern :alt: Issue Count .. image:: https://img.shields.io/pypi/dm/knittingpattern.svg :target: https://pypi.python.org/pypi/knittingpattern#downloads :alt: Downloads from pypi .. image:: https://readthedocs.org/projects/knittingpattern/badge/?version=latest :target: https://knittingpattern.readthedocs.org :alt: Read the Documentation .. image:: https://landscape.io/github/fossasia/knittingpattern/master/landscape.svg?style=flat :target: https://landscape.io/github/fossasia/knittingpattern/master :alt: Code Health .. image:: https://badge.waffle.io/fossasia/knittingpattern.svg?label=ready&title=issues%20ready :target: https://waffle.io/fossasia/knittingpattern :alt: Issues ready to work on Installation and Documentation =============== For installation instructions and more, `see the documentation `__. ================================================ FILE: appveyor.yml ================================================ # see https://packaging.python.org/appveyor/#adding-appveyor-support-to-your-project clone_depth: 1 environment: PYPI_PASSWORD: secure: Gxrd9WI60wyczr9mHtiQHvJ45Oq0UyQZNrvUtKs2D5w= PYPI_USERNAME: niccokunzmann3 matrix: # For Python versions available on Appveyor, see # http://www.appveyor.com/docs/installed-software#python # The list here is complete (excluding Python 2.6, which # isn't covered by this document) at the time of writing. - PYTHON: "C:\\Python33" UPLOAD_TO_PYPI: true - PYTHON: "C:\\Python34" UPLOAD_TO_PYPI: false - PYTHON: "C:\\Python35" UPLOAD_TO_PYPI: false # 64 bit does not make a difference # - PYTHON: "C:\\Python33-x64" # DISTUTILS_USE_SDK: "1" # - PYTHON: "C:\\Python34-x64" # DISTUTILS_USE_SDK: "1" # - PYTHON: "C:\\Python35-x64" install: # We need wheel installed to build wheels - "%PYTHON%\\python.exe -m pip install wheel" build: off test_script: # Put your test command here. # If you don't need to build C extensions on 64-bit Python 3.3 or 3.4, # you can remove "build.cmd" from the front of the command, as it's # only needed to support those cases. # Note that you must use the environment variable %PYTHON% to refer to # the interpreter you're using - Appveyor does not do anything special # to put the Python evrsion you want to use on PATH. - "%PYTHON%\\python.exe setup.py test" after_test: # This step builds your wheels. # Again, you only need build.cmd if you're building C extensions for # 64-bit Python 3.3/3.4. And you need to use %PYTHON% to get the correct # interpreter - "%PYTHON%\\python.exe setup.py bdist_wheel" artifacts: # bdist_wheel puts your built wheel in the dist directory - path: dist\* on_success: # You can use this step to upload your artifacts to a public website. # See Appveyor's documentation for more details. Or you can simply # access your wheels from the Appveyor "artifacts" tab for your build. - echo "%APPVEYOR_REPO_TAG%" - echo "%APPVEYOR_REPO_TAG_NAME%" - echo "%UPLOAD_TO_PYPI%" - set HOME=. # in https://ci.appveyor.com/project/niccokunzmann/knittingpattern/settings/environment # set the variables for the python package index http://pypi.python.org/ # PYPI_USERNAME # PYPI_PASSWORD - "IF %APPVEYOR_REPO_TAG% == true ( %PYTHON%\\python.exe -c \"import os;print('[distutils]\\r\\nindex-servers =\\r\\n pypi\\r\\n\\r\\n[pypi]\\r\\nusername:{PYPI_USERNAME}\\r\\npassword:{PYPI_PASSWORD}\\r\\n'.format(**os.environ))\" > %HOME%\\.pypirc )" # upload to pypi # check for the tags # see http://www.appveyor.com/docs/branches#build-on-tags-github-and-gitlab-only - "IF %APPVEYOR_REPO_TAG% == true ( if \"%UPLOAD_TO_PYPI%\" == \"true\" ( FOR /F %%V IN ('%PYTHON%\\python.exe setup.py --version') DO ( IF \"v%%V\" == \"%APPVEYOR_REPO_TAG_NAME%\" ( %PYTHON%\\python.exe setup.py bdist_wininst upload || echo \"Error because the build is already uploaded.\" ) ELSE ( echo \"Invalid tag %APPVEYOR_REPO_TAG_NAME% should be v%%V.\" ) ) ) ELSE ( echo \"Upload skipped.\" ) ) ELSE ( echo \"Normal build without PyPi deployment.\" )" ================================================ FILE: dev-requirements.in ================================================ pip-tools Sphinx-PyPI-upload3 autopep8 ================================================ FILE: dev-requirements.txt ================================================ # # This file is autogenerated by pip-compile # To update, run: # # pip-compile --output-file dev-requirements.txt dev-requirements.in # autopep8==1.2.4 click==6.6 # via pip-tools first==2.0.1 # via pip-tools pep8==1.7.0 # via autopep8 pip-tools==1.6.5 six==1.10.0 # via pip-tools sphinx-pypi-upload3==0.2.2 ================================================ FILE: docs/DevelopmentSetup.rst ================================================ .. _development-setup: Development Setup ================= Make sure that you have the :ref:`repository installed `. .. _development-setup-requirements: Install Requirements -------------------- To install all requirements for the development setup, execute .. code:: bash pip install --upgrade -r requirements.txt -r test-requirements.txt -r dev-requirements.txt Sphinx Documentation Setup -------------------------- Sphinx was setup using `the tutorial from readthedocs `__. It should be already setup if you completed :ref:`the previous step `. Further reading: - `domains `__ With Notepad++ under Windows, you can run the `make_html.bat `__ file in the ``docs`` directory to create the documentation and show undocumented code. Code Climate ------------ To install the code climate command line interface (cli), read about it in their github `repository `__ You need docker to be installed. Under Linux you can execute this in the Terminal to install docker: .. code:: bash wget -qO- https://get.docker.com/ | sh sudo usermod -aG docker $USER Then, log in and out. Then, you can install the command line interface: .. code:: bash wget -qO- https://github.com/codeclimate/codeclimate/archive/master.tar.gz | tar xvz cd codeclimate-* && sudo make install Then, go to the knittingpattern repository and analyze it. .. code:: bash codeclimate analyze Version Pinning --------------- We use version pinning, described in `this blog post (outdated) `__. Also read the `current version `__ for how to set up. After installation you can run .. code:: bash pip install -r requirements.in -r test-requirements.in -r dev-requirements.in pip-compile --output-file requirements.txt requirements.in pip-compile --output-file test-requirements.txt test-requirements.in pip-compile --output-file dev-requirements.txt dev-requirements.in pip-sync requirements.txt dev-requirements.txt test-requirements.txt pip install --upgrade -r requirements.txt -r test-requirements.txt -r dev-requirements.txt ``pip-sync`` uninstalls every package you do not need and writes the fix package versions to the requirements files. Continuous Integration to Pypi ------------------------------ Before you put something on `Pypi `__, ensure the following: 1. The version is in the master branch on github. 2. The tests run by travis-ci run successfully. Pypi is automatically deployed by travis. `See here `__. To upload new versions, tag them with git and push them. .. code:: bash setup.py tag_and_deploy The tag shows up as a `travis build `__. If the build succeeds, it is automatically deployed to `Pypi `__. Manual Upload to the Python Package Index ----------------------------------------- However, here you can see how to upload this package manually. Version ~~~~~~~ Throughout this chapter, ```` refers to a a string of the form ``[0-9]+\.[0-9]+\.[0-9]+[ab]?`` or ``..[]`` where ````, ```` and, ```` represent numbers and ```` can be a letter to indicate how mature the release is. 1. Create a new branch for the version. .. code:: bash git checkout -b 2. Increase the ``__version__`` in `__init__.py `__ - no letter at the end means release - ``b`` in the end means Beta - ``a`` in the end means Alpha 3. Commit and upload this version. .. _commit: .. code:: bash git add knittingpattern/__init__.py git commit -m "version " git push origin 4. Create a pull-request. 5. Wait for `travis-ci `__ to pass the tests. 6. Merge the pull-request. 7. Checkout the master branch and pull the changes from the commit_. .. code:: bash git checkout master git pull 8. Tag the version at the master branch with a ``v`` in the beginning and push it to github. .. code:: bash git tag v git push origin v 9. Upload_ the code to Pypi. Upload ~~~~~~ .. Upload: First ensure all tests are running: .. code:: bash setup.py pep8 From `docs.python.org `__: .. code:: bash setup.py sdist bdist_wininst upload register Classifiers ----------- You can find all Pypi classifiers `here `_. ================================================ FILE: docs/FileFormatSpecification.rst ================================================ .. _FileFormatSpecification: Knitting Pattern File Format Specification ========================================== For the words see `the glossary `__. Design Decisions ---------------- Concerns: - We can never implement everything that is possible with knitting. We must therefore allow instructions to be arbitrary. - We can not use a grid as a basis. This does not reflect if you split the work and make i.e. two big legs - Knitting can be done on the right and on the wrong side. The same result can be achived when knitting in both directions. Assumptions ----------- - we start from bottom right - default instruction (`see `_) .. code:: json { "type" : "knit", } { "type" : "ktog tbl", # identifier "count" : 2 } - default connection .. code:: json { "start" : 0, } - ``"id"`` can point to an object. ================================================ FILE: docs/Installation.rst ================================================ .. _installation: knittingpattern Installation Instructions ========================================= Package installation from Pypi ------------------------------ The knittingpattern library requires `Python 3 `__. It can be installed form the `Python Package Index `__. Windows ~~~~~~~ Install it with a specific python version under windows: .. code:: bash py -3 -m pip --no-cache-dir install --upgrade knittingpattern Test the installed version: .. code:: bash py -3 -m pytest --pyargs knittingpattern Linux ~~~~~ To install the version from the python package index, you can use your terminal and execute this under Linux: .. code:: shell sudo python3 -m pip --no-cache-dir install --upgrade knittingpattern test the installed version: .. code:: shell python3 -m pytest --pyargs knittingpattern .. _installation-repository: Installation from Repository ---------------------------- You can setup the development version under Windows and Linux. .. _installation-repository-linux: Linux ~~~~~ If you wish to get latest source version running, you can check out the repository and install it manually. .. code:: bash git clone https://github.com/fossasia/knittingpattern.git cd knittingpattern sudo python3 -m pip install --upgrade pip sudo python3 -m pip install -r requirements.txt sudo python3 -m pip install -r test-requirements.txt py.test To also make it importable for other libraries, you can link it into the site-packages folder this way: .. code:: bash sudo python3 setup.py link .. _installation-repository-windows: Windows ~~~~~~~ Same as under :ref:`installation-repository-linux` but you need to replace ``sudo python3`` with ``py -3``. This also counts for the following documentation. ================================================ FILE: docs/Makefile ================================================ # Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = ../build DOCDIR = . # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(DOCDIR) # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(DOCDIR) .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/knittingpattern.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/knittingpattern.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/knittingpattern" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/knittingpattern" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." ================================================ FILE: docs/_static/.gitignore ================================================ # This file is used so the folder turns up in git. ================================================ FILE: docs/_templates/.gitignore ================================================ # This file is used so the folder turns up in git. ================================================ FILE: docs/conf.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # # knittingpattern documentation build configuration file, created by # sphinx-quickstart on Thu Jun 23 09:49:51 2016. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # 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. # import os import sys HERE = os.path.dirname(__file__) sys.path.insert(0, os.path.abspath(os.path.join(HERE, '..'))) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'sphinx.ext.githubpages', 'sphinx_paramlinks', # https://pypi.python.org/pypi/sphinx-paramlinks/ # http://stackoverflow.com/a/20845306/1320237 ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'knittingpattern' copyright = '2016, AllYarnsAreBeautiful & FOSSASIA' author = 'AllYarnsAreBeautiful & FOSSASIA' # 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 = '0.0' # The full version, including alpha/beta/rc tags. # release = '8' project_version = __import__(project).__version__ version, release = project_version.rsplit(".", 1) # 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 = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # 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 = [] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # http://stackoverflow.com/q/5599254/1320237 # use the doc string in the init method # autoclass_content = 'both' # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # # from https://github.com/rtfd/readthedocs.org/blob/master/docs/conf.py on_rtd = os.environ.get('READTHEDOCS', None) == 'True' if not on_rtd: # only import and set the theme if we're building docs locally import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # 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 # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. # " v documentation" by default. # html_title = project + " " + project_version # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = project # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # TODO # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or # 32x32 pixels large. # # html_favicon = None # TODO # 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'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = False # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'knittingpatterndoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # 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', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'knittingpattern.tex', 'knittingpattern Documentation', 'AllYarnsAreBeautiful & FOSSASIA', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, project, project + ' Documentation', [author], 1) ] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, project, project + ' Documentation', author, project, 'One line description of project.', # TODO 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False # -- Options for Epub output ---------------------------------------------- # Bibliographic Dublin Core info. epub_title = project epub_author = author epub_publisher = author epub_copyright = copyright # The basename for the epub file. It defaults to the project name. # epub_basename = project # The HTML theme for the epub output. Since the default themes are not # optimized for small screen space, using the same theme for HTML and epub # output is usually not wise. This defaults to 'epub', a theme designed to save # visual space. # # epub_theme = 'epub' # The language of the text. It defaults to the language option # or 'en' if the language is not set. # # epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. # epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A tuple containing the cover image and cover page html template filenames. # # epub_cover = () # A sequence of (type, uri, title) tuples for the guide element of content.opf. # # epub_guide = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. # # epub_pre_files = [] # HTML files that should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. # # epub_post_files = [] # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # The depth of the table of contents in toc.ncx. # # epub_tocdepth = 3 # Allow duplicate toc entries. # # epub_tocdup = True # Choose between 'default' and 'includehidden'. # # epub_tocscope = 'default' # Fix unsupported image types using the Pillow. # # epub_fix_images = False # Scale large images. # # epub_max_image_width = 0 # How to display URL addresses: 'footnote', 'no', or 'inline'. # # epub_show_urls = 'inline' # If false, no index is generated. # # epub_use_index = True # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { "https://docs.python.org/3/": None, "http://observablelist.readthedocs.io/en/latest/": None, "https://kivy.org/docs/": None} ================================================ FILE: docs/index.rst ================================================ .. knittingpattern documentation master file, created by sphinx-quickstart on Thu Jun 23 09:49:51 2016. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to knittingpattern's documentation! =========================================== Contents: .. toctree:: :maxdepth: 2 Installation FileFormatSpecification DevelopmentSetup reference/index Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ================================================ FILE: docs/make.bat ================================================ @ECHO OFF REM Command file for Sphinx documentation set DOCDIR=. if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=../build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% %DOCDIR% set I18NSPHINXOPTS=%SPHINXOPTS% %DOCDIR% if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. epub3 to make an epub3 echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled echo. coverage to run coverage check of the documentation if enabled echo. dummy to check syntax errors of document sources goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) REM Check if sphinx-build is available and fallback to Python version if any %SPHINXBUILD% 1>NUL 2>NUL if errorlevel 9009 goto sphinx_python goto sphinx_ok :sphinx_python set SPHINXBUILD=python -m sphinx.__init__ %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) :sphinx_ok if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\knittingpattern.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\knittingpattern.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "epub3" ( %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "coverage" ( %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage if errorlevel 1 exit /b 1 echo. echo.Testing of coverage in the sources finished, look at the ^ results in %BUILDDIR%/coverage/python.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) if "%1" == "dummy" ( %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy if errorlevel 1 exit /b 1 echo. echo.Build finished. Dummy builder generates no files. goto end ) :end ================================================ FILE: docs/make_html.bat ================================================ @echo off REM REM This is a shortcut for notepad++ REM You can press F5 and use this as command to update the html of the docs. REM cd "%~dp0" call make html call make coverage py -c "print(open('../build/coverage/Python.txt').read())" py -c "import time;time.sleep(10);print('exit')" ================================================ FILE: docs/reference/index.rst ================================================ Reference ========= .. toctree:: :maxdepth: 2 knittingpattern/index knittingpattern/convert/index knittingpattern/Dumper/index ================================================ FILE: docs/reference/knittingpattern/Dumper/FileWrapper.rst ================================================ .. py:currentmodule:: knittingpattern.Dumper.FileWrapper :py:mod:`FileWrapper` Module ============================ .. automodule:: knittingpattern.Dumper.FileWrapper :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/Dumper/file.rst ================================================ .. py:currentmodule:: knittingpattern.Dumper.file :py:mod:`file` Module ===================== .. automodule:: knittingpattern.Dumper.file :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/Dumper/index.rst ================================================ The ``knittingpattern.Dumper`` Module Reference =============================================== .. toctree:: :maxdepth: 2 init file FileWrapper json svg xml ================================================ FILE: docs/reference/knittingpattern/Dumper/init.rst ================================================ .. py:currentmodule:: knittingpattern.Dumper :py:mod:`Dumper` Module ======================= .. automodule:: knittingpattern.Dumper :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/Dumper/json.rst ================================================ .. py:currentmodule:: knittingpattern.Dumper.json :py:mod:`json` Module ===================== .. automodule:: knittingpattern.Dumper.json :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/Dumper/svg.rst ================================================ .. py:currentmodule:: knittingpattern.Dumper.svg :py:mod:`svg` Module ==================== .. automodule:: knittingpattern.Dumper.svg :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/Dumper/xml.rst ================================================ .. py:currentmodule:: knittingpattern.Dumper.xml :py:mod:`xml` Module ==================== .. automodule:: knittingpattern.Dumper.xml :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/IdCollection.rst ================================================ .. py:currentmodule:: knittingpattern.IdCollection :py:mod:`IdCollection` Module ============================= .. automodule:: knittingpattern.IdCollection :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/Instruction.rst ================================================ .. py:currentmodule:: knittingpattern.Instruction :py:mod:`Instruction` Module ============================ .. automodule:: knittingpattern.Instruction :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/InstructionLibrary.rst ================================================ .. py:currentmodule:: knittingpattern.InstructionLibrary :py:mod:`InstructionLibrary` Module =================================== .. automodule:: knittingpattern.InstructionLibrary :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/KnittingPattern.rst ================================================ .. py:currentmodule:: knittingpattern.KnittingPattern :py:mod:`KnittingPattern` Module ================================ .. automodule:: knittingpattern.KnittingPattern :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/KnittingPatternSet.rst ================================================ .. py:currentmodule:: knittingpattern.KnittingPatternSet :py:mod:`KnittingPatternSet` Module =================================== .. automodule:: knittingpattern.KnittingPatternSet :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/Loader.rst ================================================ .. py:currentmodule:: knittingpattern.Loader :py:mod:`Loader` Module ======================= .. automodule:: knittingpattern.Loader :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/Mesh.rst ================================================ .. py:currentmodule:: knittingpattern.Mesh :py:mod:`Mesh` Module ===================== .. automodule:: knittingpattern.Mesh :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/Parser.rst ================================================ .. py:currentmodule:: knittingpattern.Parser :py:mod:`Parser` Module ======================= .. automodule:: knittingpattern.Parser :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/ParsingSpecification.rst ================================================ .. py:currentmodule:: knittingpattern.ParsingSpecification :py:mod:`ParsingSpecification` Module ===================================== .. automodule:: knittingpattern.ParsingSpecification :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/Prototype.rst ================================================ .. py:currentmodule:: knittingpattern.Prototype :py:mod:`Prototype` Module ========================== .. automodule:: knittingpattern.Prototype :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/Row.rst ================================================ .. py:currentmodule:: knittingpattern.Row :py:mod:`Row` Module ==================== .. automodule:: knittingpattern.Row :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/convert/AYABPNGBuilder.rst ================================================ .. py:currentmodule:: knittingpattern.convert.AYABPNGBuilder :py:mod:`AYABPNGBuilder` Module =============================== .. automodule:: knittingpattern.convert.AYABPNGBuilder :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/convert/AYABPNGDumper.rst ================================================ .. py:currentmodule:: knittingpattern.convert.AYABPNGDumper :py:mod:`AYABPNGDumper` Module ============================== .. automodule:: knittingpattern.convert.AYABPNGDumper :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/convert/InstructionSVGCache.rst ================================================ .. py:currentmodule:: knittingpattern.convert.InstructionSVGCache :py:mod:`InstructionSVGCache` Module ==================================== .. automodule:: knittingpattern.convert.InstructionSVGCache :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/convert/InstructionToSVG.rst ================================================ .. py:currentmodule:: knittingpattern.convert.InstructionToSVG :py:mod:`InstructionToSVG` Module ================================= .. automodule:: knittingpattern.convert.InstructionToSVG :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/convert/KnittingPatternToSVG.rst ================================================ .. py:currentmodule:: knittingpattern.convert.KnittingPatternToSVG :py:mod:`KnittingPatternToSVG` Module ===================================== .. automodule:: knittingpattern.convert.KnittingPatternToSVG :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/convert/Layout.rst ================================================ .. py:currentmodule:: knittingpattern.convert.Layout :py:mod:`Layout` Module ======================= .. automodule:: knittingpattern.convert.Layout :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/convert/SVGBuilder.rst ================================================ .. py:currentmodule:: knittingpattern.convert.SVGBuilder :py:mod:`SVGBuilder` Module =========================== .. automodule:: knittingpattern.convert.SVGBuilder :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/convert/color.rst ================================================ .. py:currentmodule:: knittingpattern.convert.color :py:mod:`color` Module ====================== .. automodule:: knittingpattern.convert.color :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/convert/image_to_knittingpattern.rst ================================================ .. py:currentmodule:: knittingpattern.convert.image_to_knittingpattern :py:mod:`image_to_knittingpattern` Module ========================================= .. automodule:: knittingpattern.convert.image_to_knittingpattern :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/convert/index.rst ================================================ The ``knittingpattern.convert`` Module Reference ================================================ .. toctree:: :maxdepth: 2 init color AYABPNGBuilder AYABPNGDumper image_to_knittingpattern InstructionToSVG InstructionSVGCache KnittingPatternToSVG Layout load_and_dump SVGBuilder ================================================ FILE: docs/reference/knittingpattern/convert/init.rst ================================================ .. py:currentmodule:: knittingpattern.convert :py:mod:`convert` Module ======================== .. automodule:: knittingpattern.convert :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/convert/load_and_dump.rst ================================================ .. py:currentmodule:: knittingpattern.convert.load_and_dump :py:mod:`load_and_dump` Module ============================== .. automodule:: knittingpattern.convert.load_and_dump :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/index.rst ================================================ The ``knittingpattern`` Module Reference ======================================== .. toctree:: :maxdepth: 2 init IdCollection Instruction InstructionLibrary KnittingPattern KnittingPatternSet Loader Mesh Parser ParsingSpecification Prototype Row utils walk ================================================ FILE: docs/reference/knittingpattern/init.rst ================================================ .. py:currentmodule:: knittingpattern :py:mod:`knittingpattern` Module ================================ .. automodule:: knittingpattern :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/utils.rst ================================================ .. py:currentmodule:: knittingpattern.utils :py:mod:`utils` Module ====================== .. automodule:: knittingpattern.utils :show-inheritance: :members: :special-members: ================================================ FILE: docs/reference/knittingpattern/walk.rst ================================================ .. py:currentmodule:: knittingpattern.walk :py:mod:`walk` Module ===================== .. automodule:: knittingpattern.walk :show-inheritance: :members: :special-members: ================================================ FILE: docs/test/test_docs.py ================================================ import os def absjoin(*args): """ :return: an absolute path to the joined arguments :param args: the parts of the path to join """ return os.path.abspath(os.path.join(*args)) PACKAGE = "knittingpattern" HERE = absjoin(os.path.dirname(__file__)) DOCS_DIRECTORY = absjoin(HERE, "..") PACKAGE_LOCATION = absjoin(DOCS_DIRECTORY, "..") PACKAGE_ROOT = absjoin(PACKAGE_LOCATION, PACKAGE) PACKAGE_DOCUMENTATION = absjoin(HERE, "..", "reference") BUILD_DIRECTORY = absjoin(PACKAGE_LOCATION, "build") COVERAGE_DIRECTORY = absjoin(BUILD_DIRECTORY, "coverage") PYTHON_COVERAGE_FILE = absjoin(COVERAGE_DIRECTORY, "python.txt") __all__ = ["PACKAGE", "HERE", "DOCS_DIRECTORY", "PACKAGE_LOCATION", "PACKAGE_ROOT", "PACKAGE_DOCUMENTATION", "BUILD_DIRECTORY", "COVERAGE_DIRECTORY", "PYTHON_COVERAGE_FILE"] ================================================ FILE: docs/test/test_documentation_sources_exist.py ================================================ #!/usr/bin/python3 """Test the coverage of documentation. No function shall be left out by the documentation. Run this module to create the missing documentation files. """ from test_docs import PACKAGE_LOCATION, PACKAGE, PACKAGE_DOCUMENTATION, \ PACKAGE_ROOT import pytest from collections import namedtuple import os def relative_module_path(absolute_path): relative_path = absolute_path[len(PACKAGE_LOCATION):] if not relative_path.startswith(PACKAGE): # remove / relative_path = relative_path[1:] assert relative_path.startswith(PACKAGE) return relative_path def module_name_and_doc(relative_path): assert relative_path.startswith(PACKAGE) file, ext = os.path.splitext(relative_path) assert ext == ".py" names = [] while file: file, name = os.path.split(file) names.insert(0, name) assert names doc = names[:-1] + [names[-1].replace("__", "") + ".rst"] doc_file = os.path.join(PACKAGE_DOCUMENTATION, *doc) if names[-1] == "__init__": del names[-1] return ".".join(names), doc_file Module = namedtuple("Module", ["absolute_path", "path", "name", "doc_file", "lines", "title"]) MODULES = [] def add_module(absolute_path): relative_path = relative_module_path(absolute_path) name, doc_path = module_name_and_doc(relative_path) if os.path.isfile(doc_path): with open(doc_path) as file: lines = file.readlines() else: lines = [] relative_name = name.rsplit(".", 1)[-1] title = ":py:mod:`{}` Module".format(relative_name) MODULES.append(Module(absolute_path, relative_path, name, doc_path, lines, title)) for dirpath, dirnames, filenames in os.walk(PACKAGE_ROOT): if "__init__.py" not in filenames: # only use module content continue for filename in filenames: if filename.endswith(".py"): add_module(os.path.join(dirpath, filename)) CREATE_MODULE_MESSAGE = "You can execute {} to create the missing "\ "documentation file.".format(__file__) @pytest.mark.parametrize('module', MODULES) def test_module_has_a_documentation_file(module): assert os.path.isfile(module.doc_file), CREATE_MODULE_MESSAGE @pytest.mark.parametrize('module', MODULES) def test_documentation_references_module(module): # assert module.lines[0].strip() == ".. py:module:: " + module.name assert module.lines[1].strip() == ".. py:currentmodule:: " + module.name @pytest.mark.parametrize('module', MODULES) def test_documentation_has_proper_title(module): assert module.lines[2].strip() == "" assert module.lines[3].strip() == module.title assert module.lines[4].strip() == "=" * len(module.title) def create_new_module_documentation(): """Create documentation so it fits the tests.""" for module in MODULES: if not os.path.isfile(module.doc_file): directory = os.path.dirname(module.doc_file) os.makedirs(directory, exist_ok=True) with open(module.doc_file, "w") as file: write = file.write write("\n") # .. py:module:: " + module.name + "\n") write(".. py:currentmodule:: " + module.name + "\n") write("\n") write(module.title + "\n") write("=" * len(module.title) + "\n") write("\n") write(".. automodule:: " + module.name + "\n") write(" :show-inheritance:\n") write(" :members:\n") write(" :special-members:\n") write("\n") create_new_module_documentation() ================================================ FILE: docs/test/test_sphinx_build.py ================================================ """Test the building process of the documentation. - All modules should be documented. - All public methods/classes/functions/constants should be documented """ from test_docs import BUILD_DIRECTORY, DOCS_DIRECTORY, PYTHON_COVERAGE_FILE import subprocess import re import shutil from pytest import fixture import os UNDOCUMENTED_PYTHON_OBJECTS = """Undocumented Python objects =========================== """ WARNING_PATTERN = b"(?:checking consistency\\.\\.\\. )?" \ b"(.*(?:WARNING|SEVERE|ERROR):.*)" def print_bytes(bytes_): """Print bytes safely as string.""" try: print(bytes_.decode()) except UnicodeDecodeError: print(bytes_) @fixture(scope="module") def sphinx_build(): """Build the documentation with sphinx and return the output.""" if os.path.exists(BUILD_DIRECTORY): shutil.rmtree(BUILD_DIRECTORY) output = subprocess.check_output( "make html", shell=True, cwd=DOCS_DIRECTORY, stderr=subprocess.STDOUT ) output += subprocess.check_output( "make coverage", shell=True, cwd=DOCS_DIRECTORY, stderr=subprocess.STDOUT ) print(output.decode()) return output @fixture(scope="module") def coverage(sphinx_build): """:return: the documentation coverage outpupt.""" assert sphinx_build, "we built before we try to access the result" with open(PYTHON_COVERAGE_FILE) as file: return file.read() @fixture def warnings(sphinx_build): """:return: the warnings during the build process.""" return re.findall(WARNING_PATTERN, sphinx_build) def test_all_methods_are_documented(coverage): """Make sure that everything that is public is also documented.""" print(coverage) assert coverage == UNDOCUMENTED_PYTHON_OBJECTS def test_doc_build_passes_without_warnings(warnings): """Make sure that the documentation is semantically correct.""" # WARNING: document isn't included in any toctree for warning in warnings: print_bytes(warning.strip()) assert warnings == [] ================================================ FILE: knittingpattern/Dumper/FileWrapper.py ================================================ """This module provides wrappers for file-like objects for encoding and decoding. """ class BytesWrapper(object): """Use this class if you have a text-file but you want to write bytes to it. """ def __init__(self, text_file, encoding): """Create a wrapper around :paramref:`text_file` that decodes bytes to string using :paramref:`encoding` and writes them to :paramref:`text_file`. :param str encoding: The encoding to use to transfer the written bytes to string so they can be written to :paramref:`text_file` :param text_file: a file-like object open in text mode """ self._file = text_file self._encoding = encoding def write(self, bytes_): """Write bytes to the file.""" string = bytes_.decode(self._encoding) self._file.write(string) class TextWrapper(object): """Use this class if you have a binary-file but you want to write strings to it. """ def __init__(self, binary_file, encoding): """Create a wrapper around :paramref:`binary_file` that encodes strings to bytes using :paramref:`encoding` and writes them to :paramref:`binary_file`. :param str encoding: The encoding to use to transfer the written string to bytes so they can be written to :paramref:`binary_file` :param binary_file: a file-like object open in binary mode """ self._file = binary_file self._encoding = encoding def write(self, string): """Write a string to the file.""" bytes_ = string.encode(self._encoding) self._file.write(bytes_) __all__ = ["TextWrapper", "BytesWrapper"] ================================================ FILE: knittingpattern/Dumper/__init__.py ================================================ """Writing objects to files This module offers a unified interface to serialize objects to strings and save them to files. """ from .file import ContentDumper as ContentDumper from .json import JSONDumper as JSONDumper from .xml import XMLDumper as XMLDumper from .svg import SVGDumper as SVGDumper __all__ = ["ContentDumper", "JSONDumper", "XMLDumper", "SVGDumper"] ================================================ FILE: knittingpattern/Dumper/file.py ================================================ """Save strings to files.""" from io import StringIO, BytesIO from tempfile import NamedTemporaryFile from .FileWrapper import BytesWrapper, TextWrapper class ContentDumper(object): """This class is a unified interface for saving objects. The idea is to decouple the place to save to from the process used to dump the content. We are saving several objects such as patterns and SVGs. They should all have the same convenient interface. The process of saving something usually requires writing to some file. However, users may want to have the result as a string, an open file, a file on the hard drive on a fixed or temporary location, posted to some url or in a zip file. This class should provide for all those needs while providing a uniform interface for the dumping. """ def __init__(self, on_dump, text_is_expected=True, encoding="UTF-8"): """Create a new dumper object with a function :paramref:`on_dump` :param on_dump: a function that takes a file-like object as argument and writes content to it. :param bool text_is_expected: whether to use text mode (:obj:`True`, default) or binary mode (:obj:`False`) for :paramref:`on_dump`. The dumper calls :paramref:`on_dump` with a file-like object every time one of its save methods, e.g. :meth:`string` or :meth:`file` is called. The file-like object in the :paramref:`file` argument supports the method ``write()`` to which the content should be written. :paramref:`text_is_expected` should be - :obj:`True` to pass a file to :paramref:`on_dump` that you can write strings to - :obj:`False` to pass a file to :paramref:`on_dump` that you can write bytes to """ self.__dump_to_file = on_dump self.__text_is_expected = text_is_expected self.__encoding = encoding @property def encoding(self): """:return: the encoding for byte to string conversion :rtype: str""" return self.__encoding def string(self): """:return: the dump as a string""" if self.__text_is_expected: return self._string() else: return self._bytes().decode(self.__encoding) def _string(self): """:return: the string from a :class:`io.StringIO`""" file = StringIO() self.__dump_to_file(file) file.seek(0) return file.read() def bytes(self): """:return: the dump as bytes.""" if self.__text_is_expected: return self.string().encode(self.__encoding) else: return self._bytes() def _bytes(self): """:return: bytes from a :class:`io.BytesIO`""" file = BytesIO() self.__dump_to_file(file) file.seek(0) return file.read() def file(self, file=None): """Saves the dump in a file-like object in text mode. :param file: :obj:`None` or a file-like object. :return: a file-like object If :paramref:`file` is :obj:`None`, a new :class:`io.StringIO` is returned. If :paramref:`file` is not :obj:`None` it should be a file-like object. The content is written to the file. After writing, the file's read/write position points behind the dumped content. """ if file is None: file = StringIO() self._file(file) return file def _file(self, file): """Dump the content to a `file`. """ if not self.__text_is_expected: file = BytesWrapper(file, self.__encoding) self.__dump_to_file(file) def binary_file(self, file=None): """Same as :meth:`file` but for binary content.""" if file is None: file = BytesIO() self._binary_file(file) return file def _binary_file(self, file): """Dump the ocntent into the `file` in binary mode. """ if self.__text_is_expected: file = TextWrapper(file, self.__encoding) self.__dump_to_file(file) def _mode_and_encoding_for_open(self): """:return: the file mode and encoding for :obj:`open`.""" if self.__text_is_expected: return "w", self.__encoding return "wb", None def path(self, path): """Saves the dump in a file named :paramref:`path`. :param str path: a valid path to a file location. The file can exist. """ self._path(path) def _path(self, path): """Saves the dump in a file named `path`.""" mode, encoding = self._mode_and_encoding_for_open() with open(path, mode, encoding=encoding) as file: self.__dump_to_file(file) def _temporary_file(self, delete): """:return: a temporary file where the content is dumped to.""" file = NamedTemporaryFile("w+", delete=delete, encoding=self.__encoding) self._file(file) return file def temporary_path(self, extension=""): """Saves the dump in a temporary file and returns its path. .. warning:: The user of this method is responsible for deleting this file to save space on the hard drive. If you only need a file object for a short period of time you can use the method :meth:`temporary_file`. :param str extension: the ending ot the file name e.g. ``".png"`` :return: a path to the temporary file :rtype: str """ path = NamedTemporaryFile(delete=False, suffix=extension).name self.path(path) return path def temporary_file(self, delete_when_closed=True): """Saves the dump in a temporary file and returns the open file object. :param bool delete_when_closed: whether to delete the temporary file when it is closed. :return: a file-like object If :paramref:`delete_when_closed` is :obj:`True` (default) the file on the hard drive will be deleted if it is closed or not referenced any more. If :paramref:`delete_when_closed` is :obj:`False` the returned temporary file is not deleted when closed or unreferenced. The user of this method has then the responsibility to free the space on the host system. The returned file-like object has an attribute ``name`` that holds the location of the file. """ return self._temporary_file(delete_when_closed) def binary_temporary_file(self, delete_when_closed=True): """Same as :meth:`temporary_file` but for binary mode.""" return self._binary_temporary_file(delete_when_closed) temporary_binary_file = binary_temporary_file def _binary_temporary_file(self, delete): """:return: a binary temporary file where the content is dumped to.""" file = NamedTemporaryFile("wb+", delete=delete) self._binary_file(file) return file def __repr__(self): """the string representation for people to read :return: the string representation of this object :rtype: str """ return "<{} in with encoding {} >".format( self.__class__.__name__, self.__encoding ) __all__ = ["ContentDumper"] ================================================ FILE: knittingpattern/Dumper/json.py ================================================ """Dump objects to JSON.""" import json from .file import ContentDumper class JSONDumper(ContentDumper): """This class can be used to dump object s as JSON.""" def __init__(self, on_dump): """Create a new JSONDumper object with the callable `on_dump`. `on_dump` takes no arguments and returns the object that should be serialized to JSON.""" super().__init__(self._dump_to_file) self.__dump_object = on_dump def object(self): """Return the object that should be dumped.""" return self.__dump_object() def _dump_to_file(self, file): """dump to the file""" json.dump(self.object(), file) def knitting_pattern(self, specification=None): """loads a :class:`knitting pattern ` from the dumped content :param specification: a :class:`~knittingpattern.ParsingSpecification.ParsingSpecification` or :obj:`None` to use the default specification""" from ..ParsingSpecification import new_knitting_pattern_set_loader if specification is None: loader = new_knitting_pattern_set_loader() else: loader = new_knitting_pattern_set_loader(specification) return loader.object(self.object()) __all__ = ["JSONDumper"] ================================================ FILE: knittingpattern/Dumper/svg.py ================================================ """Dump objects to SVG.""" from .xml import XMLDumper from os import remove as remove_file class SVGDumper(XMLDumper): """This class dumps objects to SVG.""" def kivy_svg(self): """An SVG object. :return: an SVG object :rtype: kivy.graphics.svg.Svg :raises ImportError: if the module was not found """ from kivy.graphics.svg import Svg path = self.temporary_path(".svg") try: return Svg(path) finally: remove_file(path) __all__ = ["SVGDumper"] ================================================ FILE: knittingpattern/Dumper/xml.py ================================================ """Dump objects to XML.""" import xmltodict from .file import ContentDumper class XMLDumper(ContentDumper): """Used to dump objects as XML.""" def __init__(self, on_dump): """Create a new XMLDumper object with the callable `on_dump`. `on_dump` takes no aguments and returns the object that should be serialized to XML.""" super().__init__(self._dump_to_file) self.__dump_object = on_dump def object(self): """Return the object that should be dumped.""" return self.__dump_object() def _dump_to_file(self, file): """dump to the file""" xmltodict.unparse(self.object(), file, pretty=True) __all__ = ["XMLDumper"] ================================================ FILE: knittingpattern/IdCollection.py ================================================ """See this module if you like to store object s that have an ``id`` attribute. """ from collections import OrderedDict class IdCollection(object): """This is a collections of object that have an ``id`` attribute.""" def __init__(self): """Create a new :class:`IdCollection` with no arguments. You can add objects later using the method :meth:`append`. """ self._items = OrderedDict() def append(self, item): """Add an object to the end of the :class:`IdCollection`. :param item: an object that has an id """ self._items[item.id] = item def at(self, index): """Get the object at an :paramref:`index`. :param int index: the index of the object :return: the object at :paramref:`index` """ keys = list(self._items.keys()) key = keys[index] return self[key] def __getitem__(self, id_): """Get the object with the :paramref:`id` .. code:: python ic = IdCollection() ic.append(object_1) ic.append(object_2) assert ic[object_1.id] == object_1 assert ic[object_2.id] == object_1 :param id_: the id of an object :return: the object with the :paramref:`id` :raises KeyError: if no object with :paramref:`id` was found """ return self._items[id_] def __bool__(self): """:return: whether there is anything in the collection. :rtype: bool """ return bool(self._items) def __iter__(self): """allows you to iterate and use for-loops The objects in the iterator have the order in which they were appended. """ for id_ in self._items: yield self[id_] def __len__(self): """:return: the number of objects in this collection""" return len(self._items) @property def first(self): """The first element in this collection. :return: the first element in this collection :raises IndexError: if this collection is empty """ return self.at(0) ================================================ FILE: knittingpattern/Instruction.py ================================================ """Knitting patterns consist of instructions. The :class:`instructions `. that are used in the :class:`knitting patterns ` can be foudn in this module. They have certain attributes in common. """ from .Prototype import Prototype from .Mesh import ProducedMesh, ConsumedMesh from .convert.color import convert_color_to_rrggbb # pattern specification ID = "id" #: the id key in the specification TYPE = "type" #: the type key in the specification KNIT_TYPE = "knit" #: the type of the knit instruction PURL_TYPE = "purl" #: the type of the purl instruction #: the type of the instruction without a specified type DEFAULT_TYPE = KNIT_TYPE COLOR = "color" #: the color key in the specification DESCRIPTION = "description" #: the description in the specification #: the key for the number of meshes that a instruction consumes NUMBER_OF_CONSUMED_MESHES = "number of consumed meshes" #: the default number of meshes that a instruction consumes DEFAULT_NUMBER_OF_CONSUMED_MESHES = 1 #: the key for the number of meshes that a instruction produces NUMBER_OF_PRODUCED_MESHES = "number of produced meshes" #: the default number of meshes that a instruction produces DEFAULT_NUMBER_OF_PRODUCED_MESHES = 1 #: The default z-index, see :func:`get_z`. DEFAULT_Z = 0 #: Instructions have a default specification. In this specification the key #: in :data:`RENDER` points to configuration for rendering. RENDER = "render" #: The key to look for the z-index inside the :data:`render` specification. #: .. seealso:: :func:`get_z`, :data:`DEFAULT_Z` RENDER_Z = "z" # error messages INSTRUCTION_NOT_FOUND_MESSAGE = \ "Instruction {instruction} was not found in row {row}." class Instruction(Prototype): """Instructions specify what should be done during knitting. This class represents the basic interface for instructions. It is based on the :class:`Prototype ` which allows creating instructions based on other instructions so they can inherit their attributes. You can create new instructions by passing a specification to them which can consist of a :class:`dictionary ` or an other :class:`prototype `. For such specifications see the :mod:`InstructionLibrary `. """ @property def id(self): """The id of the instruction. :return: the :data:`id ` of the instruction or :obj:`None` if none is specified. """ return self.get(ID) @property def type(self): """The type of the instruction. :return: the :data:`type ` of the instruction or :data:`DEFAULT_TYPE` if none is specified. :rtype: str The type should be a string. Depending on the type, the instruction can receive additional attributes. .. seealso:: :mod:`knittingpattern.InstructionLibrary` """ return self.get(TYPE, DEFAULT_TYPE) @property def color(self): """The color of the instruction. :return: the :data:`color ` of the instruction or :obj:`None` if none is specified. """ return self.get(COLOR) @property def colors(self): """All the colors that an instruction has. :return: a list of colors of the instruction. If the instruction has no color, this is ``[None]``. :rtype: list """ return [self.color] @property def description(self): """The description of the instruction. :return: the :data:`description ` of the instruction or :obj:`None` if none is specified. """ return self.get(DESCRIPTION) @property def number_of_consumed_meshes(self): """The number of meshes that this instruction consumes. :return: the :data:`number of consumed meshes ` of the instruction or :data:`DEFAULT_NUMBER_OF_CONSUMED_MESHES` if none is specified. """ return self.get(NUMBER_OF_CONSUMED_MESHES, DEFAULT_NUMBER_OF_CONSUMED_MESHES) @property def number_of_produced_meshes(self): """The number of meshes that this instruction produces. :return: the :data:`number of produced meshes ` of the instruction or :data:`DEFAULT_NUMBER_OF_PRODUCED_MESHES` if none is specified. """ return self.get(NUMBER_OF_PRODUCED_MESHES, DEFAULT_NUMBER_OF_PRODUCED_MESHES) def has_color(self): """Whether this instruction has a color. :return: whether a :data:`color ` is specified :rtype: bool """ return self.color is not None def does_knit(self): """Whether this instruction is a knit instruction. :return: whether this instruction is a knit instruction :rtype: bool """ return self.type == KNIT_TYPE def does_purl(self): """Whether this instruction is a purl instruction. :return: whether this instruction is a purl instruction :rtype: bool """ return self.type == PURL_TYPE def produces_meshes(self): """Whether this institution produces meshes. :return: whether this instruction produces any meshes :rtype: bool .. seealso:: :attr:`number_of_produced_meshes` """ return self.number_of_produced_meshes != 0 def consumes_meshes(self): """Whether this instruction consumes meshes. :return: whether this instruction consumes any meshes :rtype: bool .. seealso:: :attr:`number_of_consumed_meshes` """ return self.number_of_consumed_meshes != 0 @property def render_z(self): """The z-index of the instruction when rendered. :return: the z-index of the instruction. Instructions with a higher z-index are displayed in front of instructions with lower z-index. :rtype: float """ return self.get(RENDER, {}).get(RENDER_Z, DEFAULT_Z) @property def hex_color(self): """The color in "#RRGGBB" format. :return: the :attr:`color` in "#RRGGBB" format or none if no color is given """ if self.has_color(): return convert_color_to_rrggbb(self.color) return None def to_svg(self, converter=None): """Return a SVGDumper for this instruction. :param converter: a :class:` knittingpattern.convert.InstructionSVGCache.InstructionSVGCache` or :obj:`None`. If :obj:`None` is given, the :func:` knittingpattern.convert.InstructionSVGCache.default_svg_cache` is used. :rtype: knittingpattern.Dumper.SVGDumper """ if converter is None: from knittingpattern.convert.InstructionSVGCache import \ default_svg_cache converter = default_svg_cache() return converter.to_svg(self) class InstructionInRow(Instruction): """Instructions can be placed in rows. Then, they have additional attributes and properties. """ def __init__(self, row, spec): """Create a new instruction in a row with a specification. :param knittingpattern.Row.Row row: the row the instruction is placed in :param spec: specification of the instruction """ super().__init__(spec) self._row = row self._produced_meshes = [ self._new_produced_mesh(self, index) for index in range(self.number_of_produced_meshes) ] self._consumed_meshes = [ self._new_consumed_mesh(self, index) for index in range(self.number_of_consumed_meshes) ] self._cached_index_in_row = None def transfer_to_row(self, new_row): """Transfer this instruction to a new row. :param knittingpattern.Row.Row new_row: the new row the instruction is in. """ if new_row != self._row: index = self.get_index_in_row() if index is not None: self._row.instructions.pop(index) self._row = new_row @property def _new_produced_mesh(self): """:return: the class of the produced meshes.""" return ProducedMesh @property def _new_consumed_mesh(self): """:return: the class of the consumed meshes.""" return ConsumedMesh @property def row(self): """The row this instruction is in. :return: the row the instruction is placed in :rtype: knittingpattern.Row.Row """ return self._row def is_in_row(self): """Whether the instruction can be found in its row. :return: whether the instruction is in its row :rtype: bool Use this to avoid raising and :class:`InstructionNotFoundInRow`. """ return self.get_index_in_row() is not None def get_index_in_row(self): """Index of the instruction in the instructions of the row or None. :return: index in the :attr:`row`'s instructions or None, if the instruction is not in the row :rtype: int .. seealso:: :attr:`row_instructions`, :attr:`index_in_row`, :meth:`is_in_row` """ expected_index = self._cached_index_in_row instructions = self._row.instructions if expected_index is not None and \ 0 <= expected_index < len(instructions) and \ instructions[expected_index] is self: return expected_index for index, instruction_in_row in enumerate(instructions): if instruction_in_row is self: self._cached_index_in_row = index return index return None @property def index_in_row(self): """Index of the instruction in the instructions of the row. :return: index in the :attr:`row`'s instructions :rtype: int :raises knittingpattern.Instruction.InstructionNotFoundInRow: if the instruction is not found at the index .. code:: python index = instruction.index_in_row assert instruction.row.instructions[index] == instruction .. seealso:: :attr:`row_instructions`, :meth:`get_index_in_row`, :meth:`is_in_row` """ index = self.get_index_in_row() if index is None: self._raise_not_found_error() return index @property def row_instructions(self): """Shortcut for ``instruction.row.instructions``. :return: the instructions of the :attr:`row` the instruction is in .. seealso:: :attr:`index_in_row` """ return self.row.instructions @property def next_instruction_in_row(self): """The instruction after this one or None. :return: the instruction in :attr:`row_instructions` after this or :obj:`None` if this is the last :rtype: knittingpattern.Instruction.InstructionInRow This can be used to traverse the instructions. .. seealso:: :attr:`previous_instruction_in_row` """ index = self.index_in_row + 1 if index >= len(self.row_instructions): return None return self.row_instructions[index] @property def previous_instruction_in_row(self): """The instruction before this one or None. :return: the instruction in :attr:`row_instructions` before this or :obj:`None` if this is the first :rtype: knittingpattern.Instruction.InstructionInRow This can be used to traverse the instructions. .. seealso:: :attr:`next_instruction_in_row` """ index = self.index_in_row - 1 if index < 0: return None return self.row_instructions[index] @property def _instruction_not_found_message(self): """The message for the error. :return: an error message :rtype: str .. warning: private, do not use """ return INSTRUCTION_NOT_FOUND_MESSAGE.format( instruction=self, row=self.row ) def _raise_not_found_error(self): """Raise an error that this instruction is in its row no longer. :raises knittingpattern.Instruction.InstructionNotFoundInRow: the instruction was not found .. warning: private, do not use """ raise InstructionNotFoundInRow(self._instruction_not_found_message) @property def index_of_first_produced_mesh_in_row(self): """Index of the first produced mesh in the row that consumes it. :return: an index of the first produced mesh of rows produced meshes :rtype: int .. note:: If the instruction :meth:`produces meshes `, this is the index of the first mesh the instruction produces in all the meshes of the row. If the instruction does not produce meshes, the index of the mesh is returned as if the instruction had produced a mesh. .. code:: if instruction.produces_meshes(): index = instruction.index_of_first_produced_mesh_in_row """ index = 0 for instruction in self.row_instructions: if instruction is self: break index += instruction.number_of_produced_meshes else: self._raise_not_found_error() return index @property def index_of_last_produced_mesh_in_row(self): """Index of the last mesh produced by this instruction in its row. :return: an index of the last produced mesh of rows produced meshes :rtype: int .. note:: If this instruction :meth:`produces meshes `, this is the index of its last produces mesh in the row. However, if this instruction does not produce meshes, this is the index **before** the first mesh of the instruction if it produced meshes. .. seealso:: :attr:`index_of_first_produced_mesh_in_row` """ index = self.index_of_first_produced_mesh_in_row return index + self.number_of_produced_meshes - 1 @property def index_of_first_consumed_mesh_in_row(self): """The index of the first consumed mesh of this instruction in its row. Same as :attr:`index_of_first_produced_mesh_in_row` but for consumed meshes. """ index = 0 for instruction in self.row_instructions: if instruction is self: break index += instruction.number_of_consumed_meshes else: self._raise_not_found_error() return index @property def index_of_last_consumed_mesh_in_row(self): """The index of the last consumed mesh of this instruction in its row. Same as :attr:`index_of_last_produced_mesh_in_row` but for the last consumed mesh. """ index = self.index_of_first_consumed_mesh_in_row return index + self.number_of_consumed_meshes - 1 @property def produced_meshes(self): """The meshes produced by this instruction :return: a :class:`list` of :class:`meshes ` that this instruction produces :rtype: list .. code:: python assert len(inst.produced_meshes) == inst.number_of_produced_meshes assert all(mesh.is_produced() for mesh in inst.produced_meshes) .. seealso:: :attr:`consumed_meshes`, :attr:`consuming_instructions` """ return self._produced_meshes @property def consumed_meshes(self): """The meshes consumed by this instruction :return: a :class:`list` of :class:`meshes ` that this instruction consumes :rtype: list .. code:: python assert len(inst.consumed_meshes) == inst.number_of_consumed_meshes assert all(mesh.is_consumed() for mesh in inst.consumed_meshes) .. seealso:: :attr:`produced_meshes`, :attr:`producing_instructions` """ return self._consumed_meshes def __repr__(self): """:obj:`repr(instruction) ` used for :func:`print`. :return: the string representation of this object :rtype: str """ index = self.get_index_in_row() if index is None: position = "not in {}".format(self.row) else: position = "in {} at {}".format(self.row, index) return "<{} {}\"{}\" {}>".format( self.__class__.__name__, ("{} ".format(self.id) if self.id is not None else ""), self.type, position ) @property def producing_instructions(self): """Instructions that produce the meshes that this instruction consumes. :return: a list of :class:`instructions ` :rtype: list .. seealso:: :attr:`consuming_instructions`, :attr:`consumed_meshes` """ return [(mesh.producing_instruction if mesh.is_produced() else None) for mesh in self.consumed_meshes] @property def consuming_instructions(self): """Instructions that consume the meshes that this instruction produces. :return: a list of :class:`instructions ` :rtype: list .. seealso:: :attr:`producing_instructions`, :attr:`produced_meshes` """ return [(mesh.consuming_instruction if mesh.is_consumed() else None) for mesh in self.produced_meshes] @property def color(self): """The color of the instruction. :return: the :data:`color ` of the instruction or :obj:`None` if none is specified. If no color is specified in the instruction, it is inherited form the row. """ return self.get(COLOR, self.row.color) @property def last_produced_mesh(self): """The last produced mesh. :return: the last produced mesh :rtype: knittingpattern.Mesh.Mesh :raises IndexError: if no mesh is produced .. seealso:: :attr:`Instruction.number_of_produced_meshes` """ return self._produced_meshes[-1] @property def last_consumed_mesh(self): """The last consumed mesh. :return: the last consumed mesh :rtype: knittingpattern.Mesh.Mesh :raises IndexError: if no mesh is consumed .. seealso:: :attr:`Instruction.number_of_consumed_meshes` """ return self._consumed_meshes[-1] @property def first_produced_mesh(self): """The first produced mesh. :return: the first produced mesh :rtype: knittingpattern.Mesh.Mesh :raises IndexError: if no mesh is produced .. seealso:: :attr:`Instruction.number_of_produced_meshes` """ return self._produced_meshes[0] @property def first_consumed_mesh(self): """The first consumed mesh. :return: the first consumed mesh :rtype: knittingpattern.Mesh.Mesh :raises IndexError: if no mesh is consumed .. seealso:: :attr:`Instruction.number_of_consumed_meshes` """ return self._consumed_meshes[0] class InstructionNotFoundInRow(ValueError): """This exception is raised if an instruction was not found in its row.""" pass __all__ = ["Instruction", "InstructionInRow", "InstructionNotFoundInRow", "ID", "TYPE", "KNIT_TYPE", "PURL_TYPE", "DEFAULT_TYPE", "COLOR", "NUMBER_OF_CONSUMED_MESHES", "DEFAULT_NUMBER_OF_CONSUMED_MESHES", "NUMBER_OF_PRODUCED_MESHES", "DEFAULT_NUMBER_OF_PRODUCED_MESHES", "RENDER_Z", "RENDER", "DEFAULT_Z"] ================================================ FILE: knittingpattern/InstructionLibrary.py ================================================ """Instructions have many attributes that do not need to be specified in each :class:`knitting pattern set `. This module provides the functionality to load default values for instructions from various locations. """ from .Instruction import TYPE from .Loader import JSONLoader from .Instruction import Instruction class InstructionLibrary(object): """This library can be used to look up default specification of instructions. The specification is searched for by the type of the instruction. """ @property def _loader_class(self): """:return: the class for loading the specifications with :attr:`load` """ return JSONLoader @property def _instruction_class(self): """:return: the class for the specifications """ return Instruction def __init__(self): """Create a new :class:`InstructionLibrary ` without arguments. Use :attr:`load` to load specifications. """ self._type_to_instruction = {} @property def load(self): """:return: a loader that can be used to load specifications :rtype: knittingpattern.Loader.JSONLoader A file to load is a list of instructions in JSON format. .. code:: json [ { "type" : "knit", "another" : "attribute" }, { "type" : "purl" } ] """ return self._loader_class(self._process_loaded_object) def _process_loaded_object(self, obj): """add the loaded instructions from :attr:`load` """ for instruction in obj: self.add_instruction(instruction) return self def add_instruction(self, specification): """Add an instruction specification :param specification: a specification with a key :data:`knittingpattern.Instruction.TYPE` .. seealso:: :meth:`as_instruction` """ instruction = self.as_instruction(specification) self._type_to_instruction[instruction.type] = instruction def as_instruction(self, specification): """Convert the specification into an instruction :param specification: a specification with a key :data:`knittingpattern.Instruction.TYPE` The instruction is not added. .. seealso:: :meth:`add_instruction` """ instruction = self._instruction_class(specification) type_ = instruction.type if type_ in self._type_to_instruction: instruction.inherit_from(self._type_to_instruction[type_]) return instruction def __getitem__(self, instruction_type): """:return: the specification for :paramref:`instruction_type` .. seealso:: :meth:`as_instruction` """ return self.as_instruction({TYPE: instruction_type}) @property def loaded_types(self): """The types loaded in this library. :return: a list of types, preferably as :class:`string ` :rtype: list """ return list(self._type_to_instruction) class DefaultInstructions(InstructionLibrary): """The default specifications for instructions ported with this package """ #: the folder relative to this module where the instructions are located INSTRUCTIONS_FOLDER = "instructions" def __init__(self): """Create the default instruction library without arguments. The default specifications are loaded automatically form this package. """ super().__init__() self.load.relative_folder(__file__, self.INSTRUCTIONS_FOLDER) def default_instructions(): """:return: a default instruction library :rtype: DefaultInstructions .. warning:: The return value is mutable and you should not add new instructions to it. If you would like to add instructions to it, create a new :class:`~knittingpattern.InstructionLibrary.DefaultInstructions` instance. """ global _default_instructions if _default_instructions is None: _default_instructions = DefaultInstructions() return _default_instructions _default_instructions = None __all__ = ["InstructionLibrary", "DefaultInstructions", "default_instructions"] ================================================ FILE: knittingpattern/KnittingPattern.py ================================================ """Here you can find the set of knit instructions in rows. A :class:`knitting pattern set ` consists of several :class:`KnittingPatterns `. Their functionality can be found in this module. """ from .walk import walk from .utils import unique class KnittingPattern(object): """Knitting patterns contain a set of instructions that form a pattern. Usually you do not create instances of this but rather load a :class:`knitting pattern set `. """ def __init__(self, id_, name, rows, parser): """Create a new instance. :param id_: the id of this pattern :param name: the human readable name of this pattern :param rows: a collection of rows of instructions :param knittingpattern.Parser.Parser parser: the parser to use to new content .. seealso:: :func:`knittingpattern.new_knitting_pattern` """ self._id = id_ self._name = name self._rows = rows self._parser = parser @property def id(self): """the identifier within a :class:`set of knitting patterns ` """ return self._id @property def name(self): """a human readable name""" return self._name @property def rows(self): """a collection of rows that this pattern is made of Usually this should be a :class:`knittingpattern.IdCollection.IdCollection` of :class:`knittingpattern.Row.Row`.""" return self._rows def add_row(self, id_): """Add a new row to the pattern. :param id_: the id of the row """ row = self._parser.new_row(id_) self._rows.append(row) return row def rows_in_knit_order(self): """Return the rows in the order that they should be knit. :rtype: list :return: the :attr:`rows` in the order that they should be knit .. seealso:: :mod:`knittingpattern.walk` """ return walk(self) @property def instruction_colors(self): """The colors of the instructions. :return: the colors of the instructions listed in first appearance in knit order :rtype: list """ return unique([row.instruction_colors for row in self.rows_in_knit_order()]) __all__ = ["KnittingPattern"] ================================================ FILE: knittingpattern/KnittingPatternSet.py ================================================ """A set of knitting patterns that can be dumped and loaded.""" from .convert.AYABPNGDumper import AYABPNGDumper from .Dumper import XMLDumper from .convert.InstructionSVGCache import default_instruction_svg_cache from .convert.Layout import GridLayout from .convert.SVGBuilder import SVGBuilder from .convert.KnittingPatternToSVG import KnittingPatternToSVG class KnittingPatternSet(object): """This is the class for a set of knitting patterns. The :class:`knitting patterns ` all have an id and can be accessed from here. It is possible to load this set of knitting patterns from various locations, see the :mod:`knittingpattern` module. You rarely need to create such a pattern yourself. It is easier to create the pattern by loading it from a file. """ def __init__(self, type_, version, patterns, parser, comment=None): """Create a new knitting pattern set. This is the class for a set of :class:`knitting patterns `. :param str type: the type of the knitting pattern set, see the :ref:`specification `. :param str version: the version of the knitting pattern set. This is not the version of the library but the version of the :ref:`specification `. :param patterns: a collection of patterns. This should be a :class:`~knittingpattern.IdCollection.IdCollection` of :class:`KnittingPatterns `. :param comment: a comment about the knitting pattern """ self._version = version self._type = type_ self._patterns = patterns self._comment = comment self._parser = parser @property def version(self): """The version of the knitting pattern specification. :return: the version of the knitting pattern, see :meth:`__init__` :rtype: str .. seealso:: :ref:`FileFormatSpecification` """ return self._version @property def type(self): """The type of the knitting pattern. :return: the type of the knitting pattern, see :meth:`__init__` :rtype: str .. seealso:: :ref:`FileFormatSpecification` """ return self._type @property def patterns(self): """The pattern contained in this set. :return: the patterns of the knitting pattern, see :meth:`__init__` :rtype: knittingpattern.IdCollection.IdCollection The patterns can be accessed by their id. """ return self._patterns @property def comment(self): """The comment about the knitting pattern. :return: the comment for the knitting pattern set or None, see :meth:`__init__`. """ return self._comment def to_ayabpng(self): """Convert the knitting pattern to a png. :return: a dumper to save this pattern set as png for the AYAB software :rtype: knittingpattern.convert.AYABPNGDumper.AYABPNGDumper Example: .. code:: python >>> knitting_pattern_set.to_ayabpng().temporary_path() "/the/path/to/the/file.png" """ return AYABPNGDumper(lambda: self) def to_svg(self, zoom): """Create an SVG from the knitting pattern set. :param float zoom: the height and width of a knit instruction :return: a dumper to save the svg to :rtype: knittingpattern.Dumper.XMLDumper Example: .. code:: python >>> knitting_pattern_set.to_svg(25).temporary_path(".svg") "/the/path/to/the/file.svg" """ def on_dump(): """Dump the knitting pattern to the file. :return: the SVG XML structure as dictionary. """ knitting_pattern = self.patterns.at(0) layout = GridLayout(knitting_pattern) instruction_to_svg = default_instruction_svg_cache() builder = SVGBuilder() kp_to_svg = KnittingPatternToSVG(knitting_pattern, layout, instruction_to_svg, builder, zoom) return kp_to_svg.build_SVG_dict() return XMLDumper(on_dump) def add_new_pattern(self, id_, name=None): """Add a new, empty knitting pattern to the set. :param id_: the id of the pattern :param name: the name of the pattern to add or if :obj:`None`, the :paramref:`id_` is used :return: a new, empty knitting pattern :rtype: knittingpattern.KnittingPattern.KnittingPattern """ if name is None: name = id_ pattern = self._parser.new_pattern(id_, name) self._patterns.append(pattern) return pattern @property def first(self): """The first element in this set. :rtype: knittingpattern.KnittingPattern.KnittingPattern """ return self._patterns.first __all__ = ["KnittingPatternSet"] ================================================ FILE: knittingpattern/Loader.py ================================================ """One can load objects from different locations. This module provides functionality to load objects from different locations while preserving a simple interface to the consumer. """ import json import os import sys def identity(object_): """:return: the argument :param object_: the object to be returned""" return object_ def true(_): """:return: :obj:`True` :param _: can be ignored""" return True class PathLoader(object): """Load paths and folders from the local file system. The :paramref:`process ` is called with a :class:`path ` as first argument: ``process(path)``. """ def __init__(self, process=identity, chooses_path=true): """Create a PathLoader object. :param process: ``process(path)`` is called with the `path` to load. The result of :paramref:`process` is returned to the caller. The default value is :func:`identity`, so the paths are returned when loaded. :param chooses_path: ``chooses_path(path)`` is called before :paramref:`process` and returns :obj:`True` or :obj:`False` depending on whether a specific path should be loaded and passed to :paramref:`process`. """ self._process = process self._chooses_path = chooses_path def folder(self, folder): """Load all files from a folder recursively. Depending on :meth:`chooses_path` some paths may not be loaded. Every loaded path is processed and returned part of the returned list. :param str folder: the folder to load the files from :rtype: list :return: a list of the results of the processing steps of the loaded files """ result = [] for root, _, files in os.walk(folder): for file in files: path = os.path.join(root, file) if self._chooses_path(path): result.append(self.path(path)) return result def chooses_path(self, path): """:return: whether the path should be loaded :rtype: bool :param str path: the path to the file to be tested """ return self._chooses_path(path) def path(self, path): """load a :paramref:`path` and return the processed result :param str path: the path to the file to be processed :return: the result of processing step """ return self._process(path) def _relative_to_absolute(self, module_location, folder): """:return: the absolute path for the `folder` relative to the module_location. :rtype: str """ if os.path.isfile(module_location): path = os.path.dirname(module_location) elif os.path.isdir(module_location): path = module_location else: module_folder = os.path.dirname(module_location) if module_folder: path = module_folder else: __import__(module_location) module = sys.modules[module_location] path = os.path.dirname(module.__file__) absolute_path = os.path.join(path, folder) return absolute_path def relative_folder(self, module, folder): """Load a folder located relative to a module and return the processed result. :param str module: can be - a path to a folder - a path to a file - a module name :param str folder: the path of a folder relative to :paramref:`module` :return: a list of the results of the processing :rtype: list Depending on :meth:`chooses_path` some paths may not be loaded. Every loaded path is processed and returned part of the returned list. You can use :meth:`choose_paths` to find out which paths are chosen to load. """ folder = self._relative_to_absolute(module, folder) return self.folder(folder) def relative_file(self, module, file): """Load a file relative to a module. :param str module: can be - a path to a folder - a path to a file - a module name :param str folder: the path of a folder relative to :paramref:`module` :return: the result of the processing """ path = self._relative_to_absolute(module, file) return self.path(path) def choose_paths(self, paths): """:return: the paths that are chosen by :meth:`chooses_path` :rtype: list """ return [path for path in paths if self._chooses_path(path)] def example(self, relative_path): """Load an example from the knitting pattern examples. :param str relative_path: the path to load :return: the result of the processing You can use :meth:`knittingpattern.Loader.PathLoader.examples` to find out the paths of all examples. """ example_path = os.path.join("examples", relative_path) return self.relative_file(__file__, example_path) def examples(self): """Load all examples form the examples folder of this packge. :return: a list of processed examples :rtype: list Depending on :meth:`chooses_path` some paths may not be loaded. Every loaded path is processed and returned part of the returned list. """ return self.relative_folder(__file__, "examples") class ContentLoader(PathLoader): """Load contents of files and ressources. The :paramref:`process ` is called with a :class:`string ` as first argument: ``process(string)``. """ def string(self, string): """:return: the processed result of a string :param str string: the string to load the ocntent from """ return self._process(string) def file(self, file): """:return: the processed result of the content of a file-like object. :param file: the file-like object to load the content from. It should support the ``read`` method. """ string = file.read() return self.string(string) def path(self, path): """:return: the processed result of a :paramref:`path's ` content. :param str path: the path where to load the content from. It should exist on the local file system. """ with open(path) as file: return self.file(file) def url(self, url, encoding="UTF-8"): """load and process the content behind a url :return: the processed result of the :paramref:`url's ` content :param str url: the url to retrieve the content from :param str encoding: the encoding of the retrieved content. The default encoding is UTF-8. """ import urllib.request with urllib.request.urlopen(url) as file: webpage_content = file.read() webpage_content = webpage_content.decode(encoding) return self.string(webpage_content) class JSONLoader(ContentLoader): """Load an process JSON from various locations. The :paramref:`process ` is called with an :class:`object` as first argument: ``process(object)``. """ def object(self, object_): """Processes an already loaded object. :return: the result of the processing step :param object: the object to be loaded """ return self._process(object_) def string(self, string): """Load an object from a string and return the processed JSON content :return: the result of the processing step :param str string: the string to load the JSON from """ object_ = json.loads(string) return self.object(object_) __all__ = ["JSONLoader", "ContentLoader", "PathLoader", "true", "identity"] ================================================ FILE: knittingpattern/Mesh.py ================================================ """This module contains the meshes of the knit work.""" from abc import ABCMeta, abstractmethod class Mesh(metaclass=ABCMeta): """A mesh that is either consumed or produced by an instruction. .. code:: python assert mesh.is_produced() or mesh.is_consumed() Since this is an abstract base class you will only get instances of :class:`ProducedMesh ` and :class:`ConsumedMesh `. """ @abstractmethod def _producing_instruction_and_index(self): """Replace this method.""" @abstractmethod def _producing_row_and_index(self): """Replace this method.""" @abstractmethod def _consuming_instruction_and_index(self): """Replace this method.""" @abstractmethod def _consuming_row_and_index(self): """Replace this method.""" @abstractmethod def _is_produced(self): """Replace this method.""" @abstractmethod def _is_consumed(self): """Replace this method.""" @abstractmethod def _is_consumed_mesh(self): """Replace this method. :return: whether this mesh is an instance of a ConsumedMesh. """ @abstractmethod def _disconnect(self): """Replace this method.""" @abstractmethod def _connect_to(self, other_mesh): """Replace this method.""" @abstractmethod def _as_produced_mesh(self): """Replace this method.""" @abstractmethod def _as_consumed_mesh(self): """Replace this method.""" @abstractmethod def _is_connected_to(self, other_mesh): """Replace this method.""" def _assert_is_produced(self): assert self._is_produced(), "Check with is_produced() before!" def _assert_is_consumed(self): assert self._is_consumed(), "Check with is_consumed() before!" def is_produced(self): """Whether the mesh has an instruction that produces it. :return: whether the mesh is produced by an instruction :rtype: bool If you get this mesh from :attr:`knittingpattern.Instruction.InstructionInRow.produced_meshes` or :attr:`knittingpattern.Row.Row.produced_meshes`, this should be :obj:`True`. .. warning:: Before you use any methods on how the mesh is produced, you should check with ``mesh.is_produced()``. """ return self._is_produced() def is_consumed(self): """Whether the mesh has an instruction that consumed it. :return: whether the mesh is consumed by an instruction :rtype: bool If you get this mesh from :attr:`knittingpattern.Instruction.InstructionInRow.consumed_meshes` or :attr:`knittingpattern.Row.Row.consumed_meshes`, this should be :obj:`True`. .. warning:: Before you use any methods on how the mesh is consumed, you should check with ``mesh.is_consumed()``. """ return self._is_consumed() @property def index_in_producing_instruction(self): """Index in instruction as a produced mesh. :return: the index of the mesh in the list of meshes that :attr:`producing_instruction` produces :rtype: int .. code:: python instruction = mesh.producing_instruction index = mesh.index_in_producing_instruction assert instruction.produced_meshes[index] == mesh .. seealso:: :attr:`producing_instruction`, :attr:`index_in_consuming_instruction` .. warning:: Check with :meth:`is_produced` before! """ self._assert_is_produced() return self._producing_instruction_and_index()[1] @property def producing_instruction(self): """Instruction which produces this mesh. :return: the instruction that produces this mesh :rtype: knittingpattern.Instruction.InstructionInRow .. seealso:: :attr:`index_in_producing_instruction`, :attr:`producing_row`, :attr:`consuming_row` .. warning:: Check with :meth:`is_produced` before! """ self._assert_is_produced() return self._producing_instruction_and_index()[0] @property def producing_row(self): """Row which produces this mesh. :return: the row of the instruction that produces this mesh :rtype: knittingpattern.Row.Row .. seealso:: :attr:`index_in_producing_row`, :attr:`producing_instruction`, :attr:`consuming_row` .. warning:: Check with :meth:`is_produced` before! """ self._assert_is_produced() return self._producing_row_and_index()[0] @property def index_in_producing_row(self): """Index in row as produced mesh. :return: the index of the mesh in the :attr:`producing_row` :rtype: int .. code:: python row = mesh.producing_row index = mesh.index_in_producing_row assert row[index] == mesh .. seealso:: :attr:`producing_row`, :attr:`index_in_consuming_row` .. warning:: Check with :meth:`is_produced` before! """ self._assert_is_produced() return self._producing_row_and_index()[1] @property def index_in_consuming_row(self): """Index in row as consumed mesh. :return: the index of the mesh in the list of meshes that :attr:`consuming_row` consumes :rtype: int .. code:: python row = mesh.consuming_row index = mesh.index_in_consuming_row assert row.consumed_meshes[index] == mesh .. seealso:: :attr:`consuming_row`, :attr:`index_in_producing_row` .. warning:: Check with :meth:`is_consumed` before! """ self._assert_is_consumed() return self._consuming_row_and_index()[1] @property def consuming_row(self): """Row which consumes this mesh. :return: the row that consumes this mesh :rtype: knittingpattern.Row.Row .. seealso:: :attr:`index_in_consuming_row`, :attr:`consuming_instruction`, :attr:`producing_row` .. warning:: Check with :meth:`is_consumed` before! """ self._assert_is_consumed() return self._consuming_row_and_index()[0] @property def consuming_instruction(self): """Instruction which consumes this mesh. :return: the instruction that consumes this mesh :rtype: knittingpattern.Instruction.InstructionInRow .. seealso:: :attr:`index_in_consuming_instruction`, :attr:`consuming_row`, :attr:`producing_instruction` .. warning:: Check with :meth:`is_consumed` before! """ self._assert_is_consumed() return self._consuming_instruction_and_index()[0] @property def index_in_consuming_instruction(self): """Index in instruction as consumed mesh. :return: the index of the mesh in the list of meshes that :attr:`consuming_instruction` consumes :rtype: int .. code:: python instruction = mesh.consuming_instruction index = mesh.index_in_consuming_instruction assert instruction.consumed_meshes[index] == mesh .. seealso:: :attr:`consuming_instruction`, :attr:`index_in_consuming_instruction` .. warning:: Check with :meth:`is_consumed` before! """ self._assert_is_consumed() return self._consuming_instruction_and_index()[1] def is_knit(self): """Whether the mesh is produced by a knit instruction. :return: whether the mesh is knit by an instruction :rtype: bool .. seealso:: :attr:`producing_instruction` """ self._assert_is_produced() return self._producing_instruction_and_index()[0].does_knit() def __repr__(self): """This mesh as string. :return: the string representation of this mesh. :rtype: str This is useful for :func:`print` and class:`str` """ if self._is_consumed(): instruction, _ = self._consuming_instruction_and_index() row, row_index = self._consuming_row_and_index() consume_string = " for {} in {}[{}]".format( instruction, row, row_index ) else: consume_string = "" if self._is_produced(): instruction, _ = self._producing_instruction_and_index() row, row_index = self._producing_row_and_index() produce_string = " by {} in {}[{}]".format( instruction, row, row_index ) else: produce_string = "" return "<{}{}{}>".format( self.__class__.__name__, produce_string, consume_string ) def disconnect(self): """Remove the connection between two rows through this mesh. After disconnecting this mesh, it can be connected anew. """ if self.is_connected(): self._disconnect() def connect_to(self, other_mesh): """Create a connection to an other mesh. .. warning:: Both meshes need to be disconnected and one needs to be a consumed and the other a produced mesh. You can check if a connection is possible using :meth:`can_connect_to`. .. seealso:: :meth:`is_consumed`, :meth:`is_produced`, :meth:`can_connect_to` """ other_mesh.disconnect() self.disconnect() self._connect_to(other_mesh) def is_connected(self): """Returns whether this mesh is already connected. :return: whether this mesh is connected to an other. :rtype: bool """ return self._is_consumed() and self._is_produced() def as_produced_mesh(self): """The produced part to this mesh. If meshes are split up, it may be important which row the mesh is connected to afterwards. This method returns the mesh that is connected to the :attr:`producing row `. If you got this mesh from :attr:`InstructionInRow.produced_meshes ` or :attr:`Row.produced_meshes `, this returns the same object. .. seealso:: :meth:`as_consumed_mesh`, :attr:`knittinpattern.Instruction.InstructionInRow.produced_meshes`, :attr:`knittinpattern.Row.Row.produced_meshes` """ self._assert_is_produced() return self._as_produced_mesh() def as_consumed_mesh(self): """The consumed part to this mesh.""" self._assert_is_consumed() return self._as_consumed_mesh() def is_mesh(self): """Whether this object is a mesh. :return: :obj:`True` :rtype: bool """ return True def is_connected_to(self, other_mesh): """Whether the one mesh is conencted to the other.""" assert other_mesh.is_mesh() return self._is_connected_to(other_mesh) def can_connect_to(self, other): """Whether a connection can be established between those two meshes.""" assert other.is_mesh() disconnected = not other.is_connected() and not self.is_connected() types_differ = self._is_consumed_mesh() != other._is_consumed_mesh() return disconnected and types_differ class ProducedMesh(Mesh): """A :class:`~knittingpattern.Mesh.Mesh` that has a producing instruction """ def __init__(self, producing_instruction, index_in_producing_instruction): """ :param producing_instruction: the :class:`instruction ` that produces the mesh :param int index_in_producing_instruction: the index of the mesh in the list of meshes that :attr:`producing_instruction` produces .. note:: There should be no necessity to create instances of this directly. You should be able to use ``instruction.produced_meshes`` or ``instruction.consumed_meshes`` to access the :class:`meshes `. """ self.__producing_instruction_and_index = ( producing_instruction, index_in_producing_instruction ) self._consumed_part = None def _producing_instruction_and_index(self): return self.__producing_instruction_and_index def _producing_row_and_index(self): instruction, index = self.__producing_instruction_and_index producing_row = instruction.row return (producing_row, index + instruction.index_of_first_produced_mesh_in_row) def _consuming_instruction_and_index(self): return self._consumed_part._consuming_instruction_and_index() def _consuming_row_and_index(self): return self._consumed_part._consuming_row_and_index() def _is_produced(self): return True def _is_consumed(self): return self._consumed_part is not None def _is_consumed_mesh(self): return False def _disconnect(self): assert self._consumed_part is not None, "Use is_consumed() before." self._consumed_part._disconnected() self._consumed_part = None def _connect_to(self, other_mesh): assert other_mesh._is_consumed_mesh() self._consumed_part = other_mesh self._consumed_part._connect_to_produced_mesh(self) def _as_produced_mesh(self): return self def _as_consumed_mesh(self): assert self._consumed_part is not None return self._consumed_part def _is_connected_to(self, other_mesh): return other_mesh is not None and other_mesh == self._consumed_part class ConsumedMesh(Mesh): """A mesh that is only consumed by an instruction""" def __init__(self, consuming_instruction, index_in_consuming_instruction): """ :param consuming_instruction: the :class:`instruction ` that consumes the mesh :param int index_in_consuming_instruction: the index of the mesh in the list of meshes that :attr:`consuming_instruction` consumes .. note:: There should be no necessity to create instances of this directly. You should be able to use ``instruction.produced_meshes`` or ``instruction.consumed_meshes`` to access the :class:`meshes `. """ self.__consuming_instruction_and_index = ( consuming_instruction, index_in_consuming_instruction ) self._produced_part = None def _producing_instruction_and_index(self): return self._produced_part._producing_instruction_and_index() def _producing_row_and_index(self): return self._produced_part._producing_row_and_index() def _consuming_instruction_and_index(self): return self.__consuming_instruction_and_index def _consuming_row_and_index(self): instruction, index = self.__consuming_instruction_and_index consuming_row = instruction.row return ( consuming_row, index + instruction.index_of_first_consumed_mesh_in_row) def _is_produced(self): return self._produced_part is not None def _is_consumed(self): return True def _is_consumed_mesh(self): return True def _disconnect(self): assert self._produced_part is not None self._produced_part._disconnect() def _disconnected(self): self._produced_part = None def _connect_to(self, other_mesh): assert not other_mesh._is_consumed_mesh() other_mesh._connect_to(self) def _connect_to_produced_mesh(self, produced_mesh): """This is called after a connection has been established by the produced mesh.""" self._produced_part = produced_mesh def _as_produced_mesh(self): assert self._produced_part is not None return self._produced_part def _as_consumed_mesh(self): return self def _is_connected_to(self, other_mesh): if other_mesh._is_consumed_mesh(): return False return other_mesh is not self and other_mesh._is_connected_to(self) __all__ = ["Mesh", "ProducedMesh", "ConsumedMesh"] ================================================ FILE: knittingpattern/Parser.py ================================================ """In this module you can find the parsing of knitting pattern structures.""" # attributes ID = "id" #: the id of a row, an instruction or a pattern NAME = "name" #: the name of a row TYPE = "type" #: the type of an instruction or the knitting pattern set VERSION = "version" #: the version of a knitting pattern set INSTRUCTIONS = "instructions" #: the instructions in a row SAME_AS = "same as" #: pointer to a inherit from PATTERNS = "patterns" #: the patterns in the knitting pattern set ROWS = "rows" #: the rows inside a pattern CONNECTIONS = "connections" #: the connections in a pattern FROM = "from" #: the position and row a connection comes from TO = "to" #: the position and row a connection goes to START = "start" #: the mesh index the connection starts at #: the default mesh index the connection starts at if none is given DEFAULT_START = 0 MESHES = "meshes" #: the number of meshes of a connection COMMENT = "comment" #: a comment of a row, an instruction, anything # constants #: the default type of the knitting pattern set KNITTING_PATTERN_TYPE = "knitting pattern" class ParsingError(ValueError): """Mistake in the provided object to parse. This Error is raised if there is an error during the parsing for :class:`~knittingpattern.Parser.Parser`. """ class Parser(object): """Parses a knitting pattern set and anything in it.""" def __init__(self, specification): """Create a parser with a specification. :param specification: the types and classes to use for the resulting object structure, preferably a :class:`knittingpattern.ParsingSpecification.ParsingSpecification` """ self._spec = specification self._start() def _start(self): """Initialize the parsing process.""" self._instruction_library = self._spec.new_default_instructions() self._as_instruction = self._instruction_library.as_instruction self._id_cache = {} self._pattern_set = None self._inheritance_todos = [] self._instruction_todos = [] @staticmethod def _to_id(id_): """Converts the argument to a object suitable as an identifier. :return: a hashable object """ return tuple(id_) if isinstance(id_, list) else id_ def _error(self, text): """Raise an error. :raises: a specified ParsingError :param str text: the text to include in the error message """ raise self._spec.new_parsing_error(text) def knitting_pattern_set(self, values): """Parse a knitting pattern set. :param dict value: the specification of the knitting pattern set :rtype: knittingpattern.KnittingPatternSet.KnittingPatternSet :raises knittingpattern.KnittingPatternSet.ParsingError: if :paramref:`value` does not fulfill the :ref:`specification `. """ self._start() pattern_collection = self._new_pattern_collection() self._fill_pattern_collection(pattern_collection, values) self._create_pattern_set(pattern_collection, values) return self._pattern_set def _finish_inheritance(self): """Finish those who still need to inherit.""" while self._inheritance_todos: prototype, parent_id = self._inheritance_todos.pop() parent = self._id_cache[parent_id] prototype.inherit_from(parent) def _delay_inheritance(self, prototype, parent_id): """Add a deleyed inheritance that is ti be resolved later. When calling :meth:`_finish_inheritance` this inheritance chain shall be resolved. """ self._inheritance_todos.append((prototype, parent_id)) def _finish_instructions(self): """Finish those who still need to inherit.""" while self._instruction_todos: row = self._instruction_todos.pop() instructions = row.get(INSTRUCTIONS, []) row.instructions.extend(instructions) def _delay_instructions(self, row): """Add a deleyed inheritance that is ti be resolved later. When calling :meth:`_finish_instructions` this inheritance chain shall be resolved. """ self._instruction_todos.append(row) def _new_pattern_collection(self): """Create a new pattern collection. :return: a new specified pattern collection for :meth:`knitting_pattern_set` """ return self._spec.new_pattern_collection() def new_row_collection(self): """Create a new row collection. :return: a new specified row collection for the :meth:`knitting pattern ` """ return self._spec.new_row_collection() def _fill_pattern_collection(self, pattern_collection, values): """Fill a pattern collection.""" pattern = values.get(PATTERNS, []) for pattern_to_parse in pattern: parsed_pattern = self._pattern(pattern_to_parse) pattern_collection.append(parsed_pattern) def _row(self, values): """Parse a row.""" row_id = self._to_id(values[ID]) row = self._spec.new_row(row_id, values, self) if SAME_AS in values: self._delay_inheritance(row, self._to_id(values[SAME_AS])) self._delay_instructions(row) self._id_cache[row_id] = row return row def new_row(self, id_): """Create a new row with an id. :param id_: the id of the row :return: a row :rtype: knittingpattern.Row.Row """ return self._spec.new_row(id_, {}, self) def instruction_in_row(self, row, specification): """Parse an instruction. :param row: the row of the instruction :param specification: the specification of the instruction :return: the instruction in the row """ whole_instruction_ = self._as_instruction(specification) return self._spec.new_instruction_in_row(row, whole_instruction_) def _pattern(self, base): """Parse a pattern.""" rows = self._rows(base.get(ROWS, [])) self._finish_inheritance() self._finish_instructions() self._connect_rows(base.get(CONNECTIONS, [])) id_ = self._to_id(base[ID]) name = base[NAME] return self.new_pattern(id_, name, rows) def new_pattern(self, id_, name, rows=None): """Create a new knitting pattern. If rows is :obj:`None` it is replaced with the :meth:`new_row_collection`. """ if rows is None: rows = self.new_row_collection() return self._spec.new_pattern(id_, name, rows, self) def _rows(self, spec): """Parse a collection of rows.""" rows = self.new_row_collection() for row in spec: rows.append(self._row(row)) return rows def _connect_rows(self, connections): """Connect the parsed rows.""" for connection in connections: from_row_id = self._to_id(connection[FROM][ID]) from_row = self._id_cache[from_row_id] from_row_start_index = connection[FROM].get(START, DEFAULT_START) from_row_number_of_possible_meshes = \ from_row.number_of_produced_meshes - from_row_start_index to_row_id = self._to_id(connection[TO][ID]) to_row = self._id_cache[to_row_id] to_row_start_index = connection[TO].get(START, DEFAULT_START) to_row_number_of_possible_meshes = \ to_row.number_of_consumed_meshes - to_row_start_index meshes = min(from_row_number_of_possible_meshes, to_row_number_of_possible_meshes) # TODO: test all kinds of connections number_of_meshes = connection.get(MESHES, meshes) from_row_stop_index = from_row_start_index + number_of_meshes to_row_stop_index = to_row_start_index + number_of_meshes assert 0 <= from_row_start_index <= from_row_stop_index produced_meshes = from_row.produced_meshes[ from_row_start_index:from_row_stop_index] assert 0 <= to_row_start_index <= to_row_stop_index consumed_meshes = to_row.consumed_meshes[ to_row_start_index:to_row_stop_index] assert len(produced_meshes) == len(consumed_meshes) mesh_pairs = zip(produced_meshes, consumed_meshes) for produced_mesh, consumed_mesh in mesh_pairs: produced_mesh.connect_to(consumed_mesh) def _get_type(self, values): """:return: the type of a knitting pattern set.""" if TYPE not in values: self._error("No pattern type given but should be " "\"{}\"".format(KNITTING_PATTERN_TYPE)) type_ = values[TYPE] if type_ != KNITTING_PATTERN_TYPE: self._error("Wrong pattern type. Type is \"{}\" " "but should be \"{}\"" "".format(type_, KNITTING_PATTERN_TYPE)) return type_ def _get_version(self, values): """:return: the version of :paramref:`values`.""" return values[VERSION] def _create_pattern_set(self, pattern, values): """Create a new pattern set.""" type_ = self._get_type(values) version = self._get_version(values) comment = values.get(COMMENT) self._pattern_set = self._spec.new_pattern_set( type_, version, pattern, self, comment ) def default_parser(): """The parser with a default specification. :return: a parser using a :class:`knittingpattern.ParsingSpecification.DefaultSpecification` :rtype: knittingpattern.Parser.Parser """ from .ParsingSpecification import DefaultSpecification specification = DefaultSpecification() return Parser(specification) __all__ = ["Parser", "ID", "NAME", "TYPE", "VERSION", "INSTRUCTIONS", "SAME_AS", "PATTERNS", "ROWS", "CONNECTIONS", "FROM", "TO", "START", "DEFAULT_START", "MESHES", "COMMENT", "ParsingError", "default_parser"] ================================================ FILE: knittingpattern/ParsingSpecification.py ================================================ """This modules specifies how to convert JSON to knitting patterns. When parsing :class:`knitting patterns ` a lot of classes can be used. The :class:`ParsingSpecification` is the one place where to go to change a class that is used throughout the whole structure loaded by e.g. a :class:`knittingpattern.Parser.Parser`. :func:`new_knitting_pattern_set_loader` is a convinient interface for loading knitting patterns. These functions should do the same: .. code:: python # (1) load from module import knittingpattern kp = knittingpattern.load_from_file("my_pattern") # (2) load from knitting pattern from knittingpattern.ParsingSpecification import * kp = new_knitting_pattern_set_loader().file("my_pattern") """ from .Loader import JSONLoader from .Parser import Parser, ParsingError from .KnittingPatternSet import KnittingPatternSet from .IdCollection import IdCollection from .KnittingPattern import KnittingPattern from .Row import Row from .InstructionLibrary import DefaultInstructions from .Instruction import InstructionInRow class ParsingSpecification(object): """This is the specification for knitting pattern parsers. The :class:`` uses this specification to parse the knitting patterns. You can change every class in the data structure to add own functionality. """ def __init__(self, new_loader=JSONLoader, new_parser=Parser, new_parsing_error=ParsingError, new_pattern_set=KnittingPatternSet, new_pattern_collection=IdCollection, new_row_collection=IdCollection, new_pattern=KnittingPattern, new_row=Row, new_default_instructions=DefaultInstructions, new_instruction_in_row=InstructionInRow): """Create a new parsing specification.""" self.new_loader = new_loader self.new_parser = new_parser self.new_parsing_error = new_parsing_error self.new_pattern_set = new_pattern_set self.new_pattern_collection = new_pattern_collection self.new_row_collection = new_row_collection self.new_pattern = new_pattern self.new_row = new_row self.new_default_instructions = new_default_instructions self.new_instruction_in_row = new_instruction_in_row class DefaultSpecification(ParsingSpecification): """This is the default specification. It is created like pasing no arguments to :class:`ParsingSpecification`. The idea is to make the default specification easy to spot and create. """ def __init__(self): """Initialize the default specification with no arguments.""" super().__init__() @classmethod def __repr__(cls): """The string representation of the object. :return: the string representation :rtype: str """ return "<{}.{}>".format(cls.__module__, cls.__qualname__) def new_knitting_pattern_set_loader(specification=DefaultSpecification()): """Create a loader for a knitting pattern set. :param specification: a :class:`specification ` for the knitting pattern set, default :class:`DefaultSpecification` """ parser = specification.new_parser(specification) loader = specification.new_loader(parser.knitting_pattern_set) return loader __all__ = ["ParsingSpecification", "new_knitting_pattern_set_loader", "DefaultSpecification"] ================================================ FILE: knittingpattern/Prototype.py ================================================ """This module contains the :class:`~knittingpattern.Prototype.Prototype` that can be used to create inheritance on object level instead of class level. """ class Prototype(object): """This class provides inheritance of its specifications on object level. .. _prototype-key: Throughout this class `specification key` refers to a :func:`hashable ` object to look up a value in the specification. """ def __init__(self, specification, inherited_values=()): """create a new prototype :param specification: the specification of the prototype. This specification can be inherited by other prototypes. It can be a :class:`dict` or an other :class:`knittingpattern.Prototype.Prototype` or anything else that supports :meth:`__contains__` and :meth:`__getitem__` To look up a key in the specification it will be walked through 1. :paramref:`specification` 2. :paramref:`inherited_values` in order However, new lookups can be inserted at before :paramref:`inherited_values`, by calling :meth:`inherit_from`. """ self.__specification = [specification] + list(inherited_values) def get(self, key, default=None): """ :return: the value behind :paramref:`key` in the specification. If no value was found, :paramref:`default` is returned. :param key: a :ref:`specification key ` """ for base in self.__specification: if key in base: return base[key] return default def __getitem__(self, key): """``prototype[key]`` :param key: a :ref:`specification key ` :return: the value behind :paramref:`key` in the specification :raises KeyError: if no value was found """ default = [] value = self.get(key, default) if value is default: raise KeyError(key) return value def __contains__(self, key): """``key in prototype`` :param key: a :ref:`specification key ` :return: whether the key was found in the specification :rtype: bool """ default = [] value = self.get(key, default) return value is not default def inherit_from(self, new_specification): """Inherit from a :paramref:`new_specification` :param new_specification: a specification as passed to :meth:`__init__` The :paramref:`new_specification` is inserted before the first :paramref:`inherited value <__init__.inherited_values>`. If the order is 1. :paramref:`~__init__.specification` 2. :paramref:`~__init__.inherited_values` after calling ``prototype.inherit_from(new_specification)`` the lookup order is 1. :paramref:`~__init__.specification` 2. :paramref:`new_specification` 3. :paramref:`~__init__.inherited_values` """ self.__specification.insert(1, new_specification) __all__ = ["Prototype"] ================================================ FILE: knittingpattern/Row.py ================================================ """This module contains the rows of instructions of knitting patterns. The :class:`rows ` are part of :class:`knitting patterns `. They contain :class:`instructions ` and can be connected to other rows. """ from .Prototype import Prototype from itertools import chain from ObservableList import ObservableList from .utils import unique COLOR = "color" #: the color of the row #: an error message CONISTENCY_MESSAGE = "The data structure must be consistent." class Row(Prototype): """This class contains the functionality for rows. This class is used by :class:`knitting patterns `. """ def __init__(self, row_id, values, parser): """Create a new row. :param row_id: an identifier for the row :param values: the values from the specification :param list inheriting_from: a list of specifications to inherit values from, see :class:`knittingpattern.Prototype.Prototype` .. note:: Seldomly, you need to create this row on your own. You can load it with the :mod:`knittingpattern` or the :class:`knittingpattern.Parser.Parser`. """ super().__init__(values) self._id = row_id self._instructions = ObservableList() self._instructions.register_observer(self._instructions_changed) self._parser = parser def _instructions_changed(self, change): """Call when there is a change in the instructions.""" if change.adds(): for index, instruction in change.items(): if isinstance(instruction, dict): in_row = self._parser.instruction_in_row(self, instruction) self.instructions[index] = in_row else: instruction.transfer_to_row(self) @property def id(self): """The id of the row. :return: the id of the row """ return self._id @property def instructions(self): """The instructions in this row. :return: a collection of :class:`instructions inside the row ` :rtype: ObservableList.ObservableList """ return self._instructions @property def number_of_produced_meshes(self): """The number of meshes that this row produces. :return: the number of meshes that this row produces :rtype: int .. seealso:: :meth:`Instruction.number_of_produced_meshes() `, :meth:`number_of_consumed_meshes` """ return sum(instruction.number_of_produced_meshes for instruction in self.instructions) @property def number_of_consumed_meshes(self): """The number of meshes that this row consumes. :return: the number of meshes that this row consumes :rtype: int .. seealso:: :meth:`Instruction.number_of_consumed_meshes() `, :meth:`number_of_produced_meshes` """ return sum(instruction.number_of_consumed_meshes for instruction in self.instructions) @property def produced_meshes(self): """The meshes that this row produces with its instructions. :return: a collection of :class:`meshes ` that this instruction produces """ return list(chain(*(instruction.produced_meshes for instruction in self.instructions))) @property def consumed_meshes(self): """Same as :attr:`produced_meshes` but for consumed meshes.""" return list(chain(*(instruction.consumed_meshes for instruction in self.instructions))) def __repr__(self): """The string representation of this row. :return: a string representation of this row :rtype: str """ return "<{} {}>".format(self.__class__.__qualname__, self.id) @property def color(self): """The color of the row. :return: the color of the row as specified or :obj:`None` """ return self.get(COLOR) @property def instruction_colors(self): """The colors of the instructions in the row in the order tehy appear. :return: a list of colors of the knitting pattern in the order that they appear in :rtype: list """ return unique(instruction.colors for instruction in self.instructions) @property def last_produced_mesh(self): """The last produced mesh. :return: the last produced mesh :rtype: knittingpattern.Mesh.Mesh :raises IndexError: if no mesh is produced .. seealso:: :attr:`number_of_produced_meshes` """ for instruction in reversed(self.instructions): if instruction.produces_meshes(): return instruction.last_produced_mesh raise IndexError("{} produces no meshes".format(self)) @property def last_consumed_mesh(self): """The last consumed mesh. :return: the last consumed mesh :rtype: knittingpattern.Mesh.Mesh :raises IndexError: if no mesh is consumed .. seealso:: :attr:`number_of_consumed_meshes` """ for instruction in reversed(self.instructions): if instruction.consumes_meshes(): return instruction.last_consumed_mesh raise IndexError("{} consumes no meshes".format(self)) @property def first_produced_mesh(self): """The first produced mesh. :return: the first produced mesh :rtype: knittingpattern.Mesh.Mesh :raises IndexError: if no mesh is produced .. seealso:: :attr:`number_of_produced_meshes` """ for instruction in self.instructions: if instruction.produces_meshes(): return instruction.first_produced_mesh raise IndexError("{} produces no meshes".format(self)) @property def first_consumed_mesh(self): """The first consumed mesh. :return: the first consumed mesh :rtype: knittingpattern.Mesh.Mesh :raises IndexError: if no mesh is consumed .. seealso:: :attr:`number_of_consumed_meshes` """ for instruction in self.instructions: if instruction.consumes_meshes(): return instruction.first_consumed_mesh raise IndexError("{} consumes no meshes".format(self)) @property def rows_before(self): """The rows that produce meshes for this row. :rtype: list :return: a list of rows that produce meshes for this row. Each row occurs only once. They are sorted by the first occurrence in the instructions. """ rows_before = [] for mesh in self.consumed_meshes: if mesh.is_produced(): row = mesh.producing_row if rows_before not in rows_before: rows_before.append(row) return rows_before @property def rows_after(self): """The rows that consume meshes from this row. :rtype: list :return: a list of rows that consume meshes from this row. Each row occurs only once. They are sorted by the first occurrence in the instructions. """ rows_after = [] for mesh in self.produced_meshes: if mesh.is_consumed(): row = mesh.consuming_row if rows_after not in rows_after: rows_after.append(row) return rows_after @property def first_instruction(self): """The first instruction of the rows instructions. :rtype: knittingpattern.Instruction.InstructionInRow :return: the first instruction in this row's :attr:`instructions` """ return self.instructions[0] @property def last_instruction(self): """The last instruction of the rows instructions. :rtype: knittingpattern.Instruction.InstructionInRow :return: the last instruction in this row's :attr:`instructions` """ return self.instructions[-1] __all__ = ["Row", "COLOR"] ================================================ FILE: knittingpattern/__init__.py ================================================ """The knitting pattern module. Load and convert knitting patterns using the convenience functions listed below. """ # there should be no imports #: the version of the knitting pattern library __version__ = '0.1.19' #: an empty knitting pattern set as specification EMPTY_KNITTING_PATTERN_SET = {"version": "0.1", "type": "knitting pattern", "patterns": []} def load_from(): """Create a loader to load knitting patterns with. :return: the loader to load objects with :rtype: knittingpattern.Loader.JSONLoader Example: .. code:: python import knittingpattern, webbrowser k = knittingpattern.load_from().example("Cafe.json") webbrowser.open(k.to_svg(25).temporary_path(".svg")) """ from .ParsingSpecification import new_knitting_pattern_set_loader return new_knitting_pattern_set_loader() def load_from_object(object_): """Load a knitting pattern from an object. :rtype: knittingpattern.KnittingPatternSet.KnittingPatternSet """ return load_from().object(object_) def load_from_string(string): """Load a knitting pattern from a string. :rtype: knittingpattern.KnittingPatternSet.KnittingPatternSet """ return load_from().string(string) def load_from_file(file): """Load a knitting pattern from a file-like object. :rtype: knittingpattern.KnittingPatternSet.KnittingPatternSet """ return load_from().file(file) def load_from_path(path): """Load a knitting pattern from a file behind located at `path`. :rtype: knittingpattern.KnittingPatternSet.KnittingPatternSet """ return load_from().path(path) def load_from_url(url): """Load a knitting pattern from a url. :rtype: knittingpattern.KnittingPatternSet.KnittingPatternSet """ return load_from().url(url) def load_from_relative_file(module, path_relative_to): """Load a knitting pattern from a path relative to a module. :param str module: can be a module's file, a module's name or a module's path. :param str path_relative_to: is the path relative to the modules location. The result is loaded from this. :rtype: knittingpattern.KnittingPatternSet.KnittingPatternSet """ return load_from().relative_file(module, path_relative_to) def convert_from_image(colors=("white", "black")): """Convert and image to a knitting pattern. :return: a loader :rtype: knittingpattern.Loader.PathLoader :param tuple colors: the colors to convert to .. code:: python convert_from_image().path("pattern.png").path("pattern.json") convert_from_image().path("pattern.png").knitting_pattern() .. seealso:: :mod:`knittingoattern.convert.image_to_knitting_pattern` """ from .convert.image_to_knittingpattern import \ convert_image_to_knitting_pattern return convert_image_to_knitting_pattern(colors=colors) def new_knitting_pattern(id_, name=None): """Create a new knitting pattern. :return: a new empty knitting pattern. :param id_: the id of the knitting pattern :param name: the name of the knitting pattern or :obj:`None` if the :paramref:`id_` should be used :rtype: knittingpattern.KnittingPattern.KnittingPattern .. seealso:: :meth:`KnittingPatternSet.add_new_pattern() ` """ knitting_pattern_set = new_knitting_pattern_set() return knitting_pattern_set.add_new_pattern(id_, name) def new_knitting_pattern_set(): """Create a new, empty knitting pattern set. :rtype: knittingpattern.KnittingPatternSet.KnittingPatternSet :return: a new, empty knitting pattern set """ return load_from_object(EMPTY_KNITTING_PATTERN_SET) __all__ = ["load_from_object", "load_from_string", "load_from_file", "load_from_path", "load_from_url", "load_from_relative_file", "convert_from_image", "load_from", "new_knitting_pattern", "new_knitting_pattern_set"] ================================================ FILE: knittingpattern/convert/AYABPNGBuilder.py ================================================ """Convert knitting patterns to png files. These png files are used to be fed into the ayab-desktop software. They only contain which meshes will be knit with a contrast color. They just contain colors. """ import webcolors import PIL.Image from .color import convert_color_to_rrggbb class AYABPNGBuilder(object): """Convert knitting patterns to png files that only contain the color information and ``(x, y)`` coordinates. .. _png-color: Througout this class the term `color` refers to either - a valid html5 color name such as ``"black"``, ``"white"`` - colors of the form ``"#RGB"``, ``"#RRGGBB"`` and ``"#RRRGGGBBB"`` """ def __init__(self, min_x, min_y, max_x, max_y, default_color="white"): """Initialize the builder with the bounding box and a default color. .. _png-builder-bounds: ``min_x <= x < max_x`` and ``min_y <= y < max_y`` are the bounds of the instructions. Instructions outside the bounds are not rendered. Any Pixel that is not set has the :paramref:`default_color`. :param int min_x: the lower bound of the x coordinates :param int max_x: the upper bound of the x coordinates :param int min_y: the lower bound of the y coordinates :param int max_y: the upper bound of the y coordinates :param default_color: a valid :ref:`color ` """ self._min_x = min_x self._min_y = min_y self._max_x = max_x self._max_y = max_y self._default_color = default_color self._image = PIL.Image.new( "RGB", (max_x - min_x, max_y - min_y), self._convert_to_image_color(default_color)) def write_to_file(self, file): """write the png to the file :param file: a file-like object """ self._image.save(file, format="PNG") @staticmethod def _convert_color_to_rrggbb(color): """takes a :ref:`color ` and converts it into a 24 bit color "#RRGGBB" """ return convert_color_to_rrggbb(color) def _convert_rrggbb_to_image_color(self, rrggbb): """:return: the color that is used by the image""" return webcolors.hex_to_rgb(rrggbb) def _convert_to_image_color(self, color): """:return: a color that can be used by the image""" rgb = self._convert_color_to_rrggbb(color) return self._convert_rrggbb_to_image_color(rgb) def _set_pixel_and_convert_color(self, x, y, color): """set the pixel but convert the color before.""" if color is None: return color = self._convert_color_to_rrggbb(color) self._set_pixel(x, y, color) def _set_pixel(self, x, y, color): """set the color of the pixel. :param color: must be a valid color in the form of "#RRGGBB". If you need to convert color, use `_set_pixel_and_convert_color()`. """ if not self.is_in_bounds(x, y): return rgb = self._convert_rrggbb_to_image_color(color) x -= self._min_x y -= self._min_y self._image.putpixel((x, y), rgb) def set_pixel(self, x, y, color): """set the pixel at ``(x, y)`` position to :paramref:`color` If ``(x, y)`` is out of the :ref:`bounds ` this does not change the image. .. seealso:: :meth:`set_color_in_grid` """ self._set_pixel_and_convert_color(x, y, color) def is_in_bounds(self, x, y): """ :return: whether ``(x, y)`` is inside the :ref:`bounds ` :rtype: bool """ lower = self._min_x <= x and self._min_y <= y upper = self._max_x > x and self._max_y > y return lower and upper def set_color_in_grid(self, color_in_grid): """Set the pixel at the position of the :paramref:`color_in_grid` to its color. :param color_in_grid: must have the following attributes: - ``color`` is the :ref:`color ` to set the pixel to - ``x`` is the x position of the pixel - ``y`` is the y position of the pixel .. seealso:: :meth:`set_pixel`, :meth:`set_colors_in_grid` """ self._set_pixel_and_convert_color( color_in_grid.x, color_in_grid.y, color_in_grid.color) def set_colors_in_grid(self, some_colors_in_grid): """Same as :meth:`set_color_in_grid` but with a collection of colors in grid. :param iterable some_colors_in_grid: a collection of colors in grid for :meth:`set_color_in_grid` """ for color_in_grid in some_colors_in_grid: self._set_pixel_and_convert_color( color_in_grid.x, color_in_grid.y, color_in_grid.color) @property def default_color(self): """:return: the :ref:`color ` of the pixels that are not set You can set this color by passing it to the :meth:`constructor <__init__>`. """ return self._default_color __all__ = ["AYABPNGBuilder"] ================================================ FILE: knittingpattern/convert/AYABPNGDumper.py ================================================ """Dump knitting patterns to PNG files compatible with the AYAB software. """ from ..Dumper import ContentDumper from .Layout import GridLayout from .AYABPNGBuilder import AYABPNGBuilder class AYABPNGDumper(ContentDumper): """This class converts knitting patterns into PNG files.""" def __init__(self, function_that_returns_a_knitting_pattern_set): """Initialize the Dumper with a :paramref:`function_that_returns_a_knitting_pattern_set`. :param function_that_returns_a_knitting_pattern_set: a function that takes no arguments but returns a :class:`knittinpattern.KnittingPatternSet.KnittingPatternSet` When a dump is requested, the :paramref:`function_that_returns_a_knitting_pattern_set` is called and the knitting pattern set is converted and saved to the specified location. """ super().__init__(self._dump_knitting_pattern, text_is_expected=False, encoding=None) self.__on_dump = function_that_returns_a_knitting_pattern_set def _dump_knitting_pattern(self, file): """dump a knitting pattern to a file.""" knitting_pattern_set = self.__on_dump() knitting_pattern = knitting_pattern_set.patterns.at(0) layout = GridLayout(knitting_pattern) builder = AYABPNGBuilder(*layout.bounding_box) builder.set_colors_in_grid(layout.walk_instructions()) builder.write_to_file(file) def temporary_path(self, extension=".png"): return super().temporary_path(extension=extension) temporary_path.__doc__ = ContentDumper.temporary_path.__doc__ __all__ = ["AYABPNGDumper"] ================================================ FILE: knittingpattern/convert/InstructionSVGCache.py ================================================ """This module provides functionality to cache instruction SVGs.""" from .InstructionToSVG import default_instructions_to_svg from ..Dumper import SVGDumper from copy import deepcopy from collections import namedtuple _InstructionId = namedtuple("_InstructionId", ["type", "hex_color"]) class InstructionSVGCache(object): """This class is a cache for SVG instructions. If you plan too use only :meth:`instruction_to_svg_dict`, you are save to replace a :class:`knittingpsttern.convert.InstructionToSVG.InstructionToSVG` with this cache to get faster results. """ def __init__(self, instruction_to_svg=None): """Create the InstructionSVGCache. :param instruction_to_svg: an :class:`~knittingpattern.convert.InstructionToSVG.InstructionToSVG` object. If :obj:`None` is given, the :func:`default_instructions_to_svg ` is used. """ if instruction_to_svg is None: instruction_to_svg = default_instructions_to_svg() self._instruction_to_svg_dict = \ instruction_to_svg.instruction_to_svg_dict self._cache = {} def get_instruction_id(self, instruction_or_id): """The id that identifies the instruction in this cache. :param instruction_or_id: an :class:`instruction ` or an instruction id :return: a :func:`hashable ` object :rtype: tuple """ if isinstance(instruction_or_id, tuple): return _InstructionId(instruction_or_id) return _InstructionId(instruction_or_id.type, instruction_or_id.hex_color) def _new_svg_dumper(self, on_dump): """Create a new SVGDumper with the function ``on_dump``. :rtype: knittingpattern.Dumper.SVGDumper """ return SVGDumper(on_dump) def to_svg(self, instruction_or_id, i_promise_not_to_change_the_result=False): """Return the SVG for an instruction. :param instruction_or_id: either an :class:`~knittingpattern.Instruction.Instruction` or an id returned by :meth:`get_instruction_id` :param bool i_promise_not_to_change_the_result: - :obj:`False`: the result is copied, you can alter it. - :obj:`True`: the result is directly from the cache. If you change the result, other calls of this function get the changed result. :return: an SVGDumper :rtype: knittingpattern.Dumper.SVGDumper """ return self._new_svg_dumper(lambda: self.instruction_to_svg_dict( instruction_or_id, not i_promise_not_to_change_the_result)) def instruction_to_svg_dict(self, instruction_or_id, copy_result=True): """Return the SVG dict for the SVGBuilder. :param instruction_or_id: the instruction or id, see :meth:`get_instruction_id` :param bool copy_result: whether to copy the result :rtype: dict The result is cached. """ instruction_id = self.get_instruction_id(instruction_or_id) if instruction_id in self._cache: result = self._cache[instruction_id] else: result = self._instruction_to_svg_dict(instruction_id) self._cache[instruction_id] = result if copy_result: result = deepcopy(result) return result def default_instruction_svg_cache(): """Return the default InstructionSVGCache. :rtype: knittingpattern.convert.InstructionSVGCache.InstructionSVGCache """ global _default_instruction_svg_cache if _default_instruction_svg_cache is None: _default_instruction_svg_cache = InstructionSVGCache() return _default_instruction_svg_cache _default_instruction_svg_cache = None default_svg_cache = default_instruction_svg_cache __all__ = ["InstructionSVGCache", "default_instruction_svg_cache", "default_svg_cache"] ================================================ FILE: knittingpattern/convert/InstructionToSVG.py ================================================ """This module maps instructions to SVG. Use :func:`default_instructions_to_svg` to load the svg files provided by this package. """ import os import xmltodict from knittingpattern.Loader import PathLoader #: The string to replace with the pattern name in the SVG file. REPLACE_IN_DEFAULT_SVG = "{instruction.type}" class InstructionToSVG(object): """This class maps instructions to SVGs.""" @property def _loader_class(self): """:return: the loader to load svgs from different locations :rtype: knittingpattern.Loader.PathLoader .""" return PathLoader def __init__(self): """create a InstructionToSVG object without arguments.""" self._instruction_type_to_file_content = {} @property def load(self): """:return: a loader object that allows loading SVG files from various sources such as files and folders. :rtype: knittingpattern.Loader.PathLoader Examples: - ``instruction_to_svg.load.path(path)`` loads an SVG from a file named path - ``instruction_to_svg.load.folder(path)`` loads all SVG files for instructions in the folder recursively. If multiple files have the same name, the last occurrence is used. """ return self._loader_class(self._process_loaded_object) def _process_loaded_object(self, path): """process the :paramref:`path`. :param str path: the path to load an svg from """ file_name = os.path.basename(path) name = os.path.splitext(file_name)[0] with open(path) as file: string = file.read() self._instruction_type_to_file_content[name] = string def instruction_to_svg_dict(self, instruction): """ :return: an xml-dictionary with the same content as :meth:`instruction_to_svg`. """ instruction_type = instruction.type if instruction_type in self._instruction_type_to_file_content: svg = self._instruction_type_to_file_content[instruction_type] return self._set_fills_in_color_layer(svg, instruction.hex_color) return self.default_instruction_to_svg_dict(instruction) def instruction_to_svg(self, instruction): """:return: an SVG representing the instruction. The SVG file is determined by the type attribute of the instruction. An instruction of type ``"knit"`` is looked for in a file named ``"knit.svg"``. Every element inside a group labeled ``"color"`` of mode ``"layer"`` that has a ``"fill"`` style gets this fill replaced by the color of the instruction. Example of a recangle that gets filled like the instruction: .. code:: xml If nothing was loaded to display this instruction, a default image is be generated by :meth:`default_instruction_to_svg`. """ return xmltodict.unparse(self.instruction_to_svg_dict(instruction)) def _set_fills_in_color_layer(self, svg_string, color): """replaces fill colors in ```` with :paramref:`color` :param color: a color fill the objects in the layer with """ structure = xmltodict.parse(svg_string) if color is None: return structure layers = structure["svg"]["g"] if not isinstance(layers, list): layers = [layers] for layer in layers: if not isinstance(layer, dict): continue if layer.get("@inkscape:label") == "color" and \ layer.get("@inkscape:groupmode") == "layer": for key, elements in layer.items(): if key.startswith("@") or key.startswith("#"): continue if not isinstance(elements, list): elements = [elements] for element in elements: style = element.get("@style", None) if style: style = style.split(";") processed_style = [] for style_element in style: if style_element.startswith("fill:"): style_element = "fill:" + color processed_style.append(style_element) style = ";".join(processed_style) element["@style"] = style return structure def has_svg_for_instruction(self, instruction): """:return: whether there is an image for the instruction :rtype: bool This can be used before :meth:`instruction_to_svg` as it determines whether - the default value is used (:obj:`False`) - or there is a dedicated svg representation (:obj:`True`). """ instruction_type = instruction.type return instruction_type in self._instruction_type_to_file_content def default_instruction_to_svg(self, instruction): """As :meth:`instruction_to_svg` but it only takes the ``default.svg`` file into account. In case no file is found for an instruction in :meth:`instruction_to_svg`, this method is used to determine the default svg for it. The content is created by replacing the text ``{instruction.type}`` in the whole svg file named ``default.svg``. If no file ``default.svg`` was loaded, an empty string is returned. """ svg_dict = self.default_instruction_to_svg_dict(instruction) return xmltodict.unparse(svg_dict) def default_instruction_to_svg_dict(self, instruction): """Returns an xml-dictionary with the same content as :meth:`default_instruction_to_svg` If no file ``default.svg`` was loaded, an empty svg-dict is returned. """ instruction_type = instruction.type default_type = "default" rep_str = "{instruction.type}" if default_type not in self._instruction_type_to_file_content: return {"svg": ""} default_svg = self._instruction_type_to_file_content[default_type] default_svg = default_svg.replace(rep_str, instruction_type) colored_svg = self._set_fills_in_color_layer(default_svg, instruction.hex_color) return colored_svg #: The name of the folder containing the svg files for the default #: instructions. DEFAULT_SVG_FOLDER = "instruction-svgs" def default_instructions_to_svg(): """load the default set of svg files for instructions :return: the default svg files for the instructions in this package :rtype: knittingpattern.InstructionToSVG.InstructionToSVG """ instruction_to_svg = InstructionToSVG() instruction_to_svg.load.relative_folder(__name__, DEFAULT_SVG_FOLDER) return instruction_to_svg __all__ = ["InstructionToSVG", "default_instructions_to_svg", "DEFAULT_SVG_FOLDER"] ================================================ FILE: knittingpattern/convert/KnittingPatternToSVG.py ================================================ """This module provides functionality to convert knitting patterns to SVG.""" from collections import OrderedDict #: Inside the svg, the instructions are put into definitions. #: The svg tag is renamed to the tag given in :data:`DEFINITION_HOLDER`. DEFINITION_HOLDER = "g" class KnittingPatternToSVG(object): """Converts a KnittingPattern to SVG. This is inspired by the method object pattern, since building an SVG requires several steps. """ def __init__(self, knittingpattern, layout, instruction_to_svg, builder, zoom): """ :param knittingpattern.KnittingPattern.KnittingPattern knittingpattern: a knitting pattern :param knittingpattern.convert.Layout.GridLayout layout: :param instruction_to_svg: an :class:`~knittingpattern.convert.InstructionToSVG.InstructionToSVG` :class:` ~knittingpattern.convert.InstructionToSVGCache.InstructionSVGCache`, both with instructions already loaded. :param knittingpattern.convert.SVGBuilder.SVGBuilder builder: :param float zoom: the height and width of a knit instruction """ self._knittingpattern = knittingpattern self._layout = layout self._instruction_to_svg = instruction_to_svg self._builder = builder self._zoom = zoom self._instruction_type_color_to_symbol = OrderedDict() self._symbol_id_to_scale = {} def build_SVG_dict(self): """Go through the layout and build the SVG. :return: an xml dict that can be exported using a :class:`~knittingpattern.Dumper.XMLDumper` :rtype: dict """ zoom = self._zoom layout = self._layout builder = self._builder bbox = list(map(lambda f: f * zoom, layout.bounding_box)) builder.bounding_box = bbox flip_x = bbox[2] + bbox[0] * 2 flip_y = bbox[3] + bbox[1] * 2 instructions = list(layout.walk_instructions( lambda i: (flip_x - (i.x + i.width) * zoom, flip_y - (i.y + i.height) * zoom, i.instruction))) instructions.sort(key=lambda x_y_i: x_y_i[2].render_z) for x, y, instruction in instructions: render_z = instruction.render_z z_id = ("" if not render_z else "-{}".format(render_z)) layer_id = "row-{}{}".format(instruction.row.id, z_id) def_id = self._register_instruction_in_defs(instruction) scale = self._symbol_id_to_scale[def_id] group = { "@class": "instruction", "@id": "instruction-{}".format(instruction.id), "@transform": "translate({},{}),scale({})".format( x, y, scale) } builder.place_svg_use(def_id, layer_id, group) builder.insert_defs(self._instruction_type_color_to_symbol.values()) return builder.get_svg_dict() def _register_instruction_in_defs(self, instruction): """Create a definition for the instruction. :return: the id of a symbol in the defs for the specified :paramref:`instruction` :rtype: str If no symbol yet exists in the defs for the :paramref:`instruction` a symbol is created and saved using :meth:`_make_symbol`. """ type_ = instruction.type color_ = instruction.color instruction_to_svg_dict = \ self._instruction_to_svg.instruction_to_svg_dict instruction_id = "{}:{}".format(type_, color_) defs_id = instruction_id + ":defs" if instruction_id not in self._instruction_type_color_to_symbol: svg_dict = instruction_to_svg_dict(instruction) self._compute_scale(instruction_id, svg_dict) symbol = self._make_definition(svg_dict, instruction_id) self._instruction_type_color_to_symbol[defs_id] = \ symbol[DEFINITION_HOLDER].pop("defs", {}) self._instruction_type_color_to_symbol[instruction_id] = symbol return instruction_id def _make_definition(self, svg_dict, instruction_id): """Create a symbol out of the supplied :paramref:`svg_dict`. :param dict svg_dict: dictionary containing the SVG for the instruction currently processed :param str instruction_id: id that will be assigned to the symbol """ instruction_def = svg_dict["svg"] blacklisted_elements = ["sodipodi:namedview", "metadata"] whitelisted_attributes = ["@sodipodi:docname"] symbol = OrderedDict({"@id": instruction_id}) for content, value in instruction_def.items(): if content.startswith('@'): if content in whitelisted_attributes: symbol[content] = value elif content not in blacklisted_elements: symbol[content] = value return {DEFINITION_HOLDER: symbol} def _compute_scale(self, instruction_id, svg_dict): """Compute the scale of an instruction svg. Compute the scale using the bounding box stored in the :paramref:`svg_dict`. The scale is saved in a dictionary using :paramref:`instruction_id` as key. :param str instruction_id: id identifying a symbol in the defs :param dict svg_dict: dictionary containing the SVG for the instruction currently processed """ bbox = list(map(float, svg_dict["svg"]["@viewBox"].split())) scale = self._zoom / (bbox[3] - bbox[1]) self._symbol_id_to_scale[instruction_id] = scale __all__ = ["KnittingPatternToSVG", "DEFINITION_HOLDER"] ================================================ FILE: knittingpattern/convert/Layout.py ================================================ """Map ``(x, y)`` coordinates to instructions """ from itertools import chain from collections import namedtuple INSTRUCTION_HEIGHT = 1 #: the default height of an instruction in the grid #: This is the key to the "grid-layout" #: #: Values for the layout can be specified in each instruction. #: #: "grid-layout" : { #: "width" : 1 #: } #: #: .. seealso:: :data:`width` GRID_LAYOUT = "grid-layout" #: the width of the instruction in the grid layout, if specified. #: .. seealso:: :data:`GRID_LAYOUT` WIDTH = "width" Point = namedtuple("Point", ["x", "y"]) class InGrid(object): """Base class for things in a grid""" def __init__(self, position): """Create a new InGrid object.""" self._position = position @property def x(self): """:return: x coordinate in the grid :rtype: float """ return self._position.x @property def y(self): """:return: y coordinate in the grid :rtype: float """ return self._position.y @property def xy(self): """:return: ``(x, y)`` coordinate in the grid :rtype: tuple """ return self._position @property def yx(self): """:return: ``(y, x)`` coordinate in the grid :rtype: tuple """ return self._position.y, self._position.x @property def width(self): """:return: width of the object on the grid :rtype: float """ return self._width @property def height(self): """:return: height of the object on the grid :rtype: float """ return INSTRUCTION_HEIGHT @property def row(self): """:return: row of the object on the grid :rtype: knittingpattern.Row.Row """ return self._row @property def bounding_box(self): """The bounding box of this object. :return: (min x, min y, max x, max y) :rtype: tuple """ return self._bounding_box @property def id(self): """The id of this object.""" return self._id class InstructionInGrid(InGrid): """Holder of an instruction in the GridLayout.""" def __init__(self, instruction, position): """ :param instruction: an :class:`instruction ` :param Point position: the position of the :paramref:`instruction` """ self._instruction = instruction super().__init__(position) @property def _width(self): """For ``self.width``.""" layout = self._instruction.get(GRID_LAYOUT) if layout is not None: width = layout.get(WIDTH) if width is not None: return width return self._instruction.number_of_consumed_meshes @property def instruction(self): """The instruction. :return: instruction that is placed on the grid :rtype: knittingpattern.Instruction.InstructionInRow """ return self._instruction @property def color(self): """The color of the instruction. :return: the color of the :attr:`instruction` """ return self._instruction.color def _row(self): """For ``self.row``.""" return self._instruction.row class RowInGrid(InGrid): """Assign x and y coordinates to rows.""" def __init__(self, row, position): """Create a new row in the grid.""" super().__init__(position) self._row = row @property def _width(self): """:return: the number of consumed meshes""" return sum(map(lambda i: i.width, self.instructions)) @property def instructions(self): """The instructions in a grid. :return: the :class:`instructions in a grid ` of this row :rtype: list """ x = self.x y = self.y result = [] for instruction in self._row.instructions: instruction_in_grid = InstructionInGrid(instruction, Point(x, y)) x += instruction_in_grid.width result.append(instruction_in_grid) return result @property def _bounding_box(self): min_x = self.x min_y = self.y max_x = min_x + max(self._row.number_of_consumed_meshes, self._row.number_of_produced_meshes) max_y = min_y + self.height return min_x, min_y, max_x, max_y @property def _id(self): return self._row.id def identity(object_): """:return: the argument""" return object_ class _RecursiveWalk(object): """This class starts walking the knitting pattern and maps instructions to positions in the grid that is created.""" def __init__(self, first_instruction): """Start walking the knitting pattern starting from first_instruction. """ self._rows_in_grid = {} self._todo = [] self._expand(first_instruction.row, Point(0, 0), []) self._walk() def _expand(self, row, consumed_position, passed): """Add the arguments `(args, kw)` to `_walk` to the todo list.""" self._todo.append((row, consumed_position, passed)) def _step(self, row, position, passed): """Walk through the knitting pattern by expanding an row.""" if row in passed or not self._row_should_be_placed(row, position): return self._place_row(row, position) passed = [row] + passed # print("{}{} at\t{} {}".format(" " * len(passed), row, position, # passed)) for i, produced_mesh in enumerate(row.produced_meshes): self._expand_produced_mesh(produced_mesh, i, position, passed) for i, consumed_mesh in enumerate(row.consumed_meshes): self._expand_consumed_mesh(consumed_mesh, i, position, passed) def _expand_consumed_mesh(self, mesh, mesh_index, row_position, passed): """expand the consumed meshes""" if not mesh.is_produced(): return row = mesh.producing_row position = Point( row_position.x + mesh.index_in_producing_row - mesh_index, row_position.y - INSTRUCTION_HEIGHT ) self._expand(row, position, passed) def _expand_produced_mesh(self, mesh, mesh_index, row_position, passed): """expand the produced meshes""" if not mesh.is_consumed(): return row = mesh.consuming_row position = Point( row_position.x - mesh.index_in_consuming_row + mesh_index, row_position.y + INSTRUCTION_HEIGHT ) self._expand(row, position, passed) def _row_should_be_placed(self, row, position): """:return: whether to place this instruction""" placed_row = self._rows_in_grid.get(row) return placed_row is None or placed_row.y < position.y def _place_row(self, row, position): """place the instruction on a grid""" self._rows_in_grid[row] = RowInGrid(row, position) def _walk(self): """Loop through all the instructions that are `_todo`.""" while self._todo: args = self._todo.pop(0) self._step(*args) def instruction_in_grid(self, instruction): """Returns an `InstructionInGrid` object for the `instruction`""" row_position = self._rows_in_grid[instruction.row].xy x = instruction.index_of_first_consumed_mesh_in_row position = Point(row_position.x + x, row_position.y) return InstructionInGrid(instruction, position) def row_in_grid(self, row): """Returns an `RowInGrid` object for the `row`""" return self._rows_in_grid[row] class Connection(object): """a connection between two :class:`InstructionInGrid` objects""" def __init__(self, start, stop): """ :param InstructionInGrid start: the start of the connection :param InstructionInGrid stop: the end of the connection """ self._start = start self._stop = stop @property def start(self): """:return: the start of the connection :rtype: InstructionInGrid """ return self._start @property def stop(self): """:return: the end of the connection :rtype: InstructionInGrid """ return self._stop def is_visible(self): """:return: is this connection is visible :rtype: bool A connection is visible if it is longer that 0.""" if self._start.y + 1 < self._stop.y: return True return False class GridLayout(object): """This class places the instructions at ``(x, y)`` positions.""" def __init__(self, pattern): """ :param knittingpattern.KnittingPattern.KnittingPattern pattern: the pattern to layout """ self._pattern = pattern self._rows = list(pattern.rows) self._walk = _RecursiveWalk(self._rows[0].instructions[0]) self._rows.sort(key=lambda row: self._walk.row_in_grid(row).yx) def walk_instructions(self, mapping=identity): """Iterate over instructions. :return: an iterator over :class:`instructions in grid ` :param mapping: funcion to map the result .. code:: python for pos, c in layout.walk_instructions(lambda i: (i.xy, i.color)): print("color {} at {}".format(c, pos)) """ instructions = chain(*self.walk_rows(lambda row: row.instructions)) return map(mapping, instructions) def walk_rows(self, mapping=identity): """Iterate over rows. :return: an iterator over :class:`rows ` :param mapping: funcion to map the result, see :meth:`walk_instructions` for an example usage """ row_in_grid = self._walk.row_in_grid return map(lambda row: mapping(row_in_grid(row)), self._rows) def walk_connections(self, mapping=identity): """Iterate over connections between instructions. :return: an iterator over :class:`connections ` between :class:`instructions in grid ` :param mapping: funcion to map the result, see :meth:`walk_instructions` for an example usage """ for start in self.walk_instructions(): for stop_instruction in start.instruction.consuming_instructions: if stop_instruction is None: continue stop = self._walk.instruction_in_grid(stop_instruction) connection = Connection(start, stop) if connection.is_visible(): # print("connection:", # connection.start.instruction, # connection.stop.instruction) yield mapping(connection) @property def bounding_box(self): """The minimum and maximum bounds of this layout. :return: ``(min_x, min_y, max_x, max_y)`` the bounding box of this layout :rtype: tuple """ min_x, min_y, max_x, max_y = zip(*list(self.walk_rows( lambda row: row.bounding_box))) return min(min_x), min(min_y), max(max_x), max(max_y) def row_in_grid(self, row): """The a RowInGrid for the row with position information. :return: a row in the grid :rtype: RowInGrid """ return self._walk.row_in_grid(row) __all__ = ["GridLayout", "InstructionInGrid", "Connection", "identity", "Point", "INSTRUCTION_HEIGHT", "InGrid", "RowInGrid"] ================================================ FILE: knittingpattern/convert/SVGBuilder.py ================================================ """build SVG files """ import xmltodict #: an empty svg file as a basis SVG_FILE = """ knittingpattern """ class SVGBuilder(object): """This class builds an SVG to a file. The class itself does not know what the objects look like. It offers a more convinient interface to build SVG files. """ def __init__(self): """Initialize this object without arguments.""" self._structure = xmltodict.parse(SVG_FILE) self._layer_id_to_layer = {} self._svg = self._structure["svg"] self._min_x = None self._min_y = None self._max_x = None self._max_y = None @property def bounding_box(self): """the bounding box of this SVG ``(min_x, min_y, max_x, max_y)``. .. code:: python svg_builder10x10.bounding_box = (0, 0, 10, 10) assert svg_builder10x10.bounding_box == (0, 0, 10, 10) ``viewBox``, ``width`` and ``height`` are computed from this. If the bounding box was never set, the result is a tuple of four :obj:`None`. """ return (self._min_x, self._min_y, self._max_x, self._max_y) @bounding_box.setter def bounding_box(self, bbox): min_x, min_y, max_x, max_y = bbox self._min_x = min_x self._min_y = min_y self._max_x = max_x self._max_y = max_y self._svg["@height"] = str(max_y - min_y) self._svg["@width"] = str(max_x - min_x) self._svg["@viewBox"] = "{} {} {} {}".format(min_x, min_y, max_x, max_y) def place(self, x, y, svg, layer_id): """Place the :paramref:`svg` content at ``(x, y)`` position in the SVG, in a layer with the id :paramref:`layer_id`. :param float x: the x position of the svg :param float y: the y position of the svg :param str svg: the SVG to place at ``(x, y)`` :param str layer_id: the id of the layer that this :paramref:`svg` should be placed inside """ content = xmltodict.parse(svg) self.place_svg_dict(x, y, content, layer_id) def place_svg_dict(self, x, y, svg_dict, layer_id, group=None): """Same as :meth:`place` but with a dictionary as :paramref:`svg_dict`. :param dict svg_dict: a dictionary returned by `xmltodict.parse() `__ :param dict group: a dictionary of values to add to the group the :paramref:`svg_dict` will be added to or :obj:`None` if nothing should be added """ if group is None: group = {} group_ = { "@transform": "translate({},{})".format(x, y), "g": list(svg_dict.values()) } group_.update(group) layer = self._get_layer(layer_id) layer["g"].append(group_) def place_svg_use_coords(self, x, y, symbol_id, layer_id, group=None): """Similar to :meth:`place` but with an id as :paramref:`symbol_id`. :param str symbol_id: an id which identifies an svg object defined in the defs :param dict group: a dictionary of values to add to the group the use statement will be added to or :obj:`None` if nothing should be added """ if group is None: group = {} use = {"@x": x, "@y": y, "@xlink:href": "#{}".format(symbol_id)} group_ = {"use": use} group_.update(group) layer = self._get_layer(layer_id) layer["g"].append(group_) def place_svg_use(self, symbol_id, layer_id, group=None): """Same as :meth:`place_svg_use_coords`. With implicit `x` and `y` which are set to `0` in this method and then :meth:`place_svg_use_coords` is called. """ self.place_svg_use_coords(0, 0, symbol_id, layer_id, group) def _get_layer(self, layer_id): """ :return: the layer with the :paramref:`layer_id`. If the layer does not exist, it is created. :param str layer_id: the id of the layer """ if layer_id not in self._layer_id_to_layer: self._svg.setdefault("g", []) layer = { "g": [], "@inkscape:label": layer_id, "@id": layer_id, "@inkscape:groupmode": "layer", "@class": "row" } self._layer_id_to_layer[layer_id] = layer self._svg["g"].append(layer) return self._layer_id_to_layer[layer_id] def insert_defs(self, defs): """Adds the defs to the SVG structure. :param defs: a list of SVG dictionaries, which contain the defs, which should be added to the SVG structure. """ if self._svg["defs"] is None: self._svg["defs"] = {} for def_ in defs: for key, value in def_.items(): if key.startswith("@"): continue if key not in self._svg["defs"]: self._svg["defs"][key] = [] if not isinstance(value, list): value = [value] self._svg["defs"][key].extend(value) def get_svg_dict(self): """Return the SVG structure generated.""" return self._structure def write_to_file(self, file): """Writes the current SVG to the :paramref:`file`. :param file: a file-like object """ xmltodict.unparse(self._structure, file, pretty=True) __all__ = ["SVGBuilder", "SVG_FILE"] ================================================ FILE: knittingpattern/convert/__init__.py ================================================ """Convert knitting patterns. Usually you do not need to import this. Convenience functions should be available in the :mod:`knittingpattern` module. """ ================================================ FILE: knittingpattern/convert/color.py ================================================ """Functions for color conversion.""" import webcolors def convert_color_to_rrggbb(color): """The color in "#RRGGBB" format. :return: the :attr:`color` in "#RRGGBB" format """ if not color.startswith("#"): rgb = webcolors.html5_parse_legacy_color(color) hex_color = webcolors.html5_serialize_simple_color(rgb) else: hex_color = color return webcolors.normalize_hex(hex_color) __all__ = ["convert_color_to_rrggbb"] ================================================ FILE: knittingpattern/convert/image_to_knittingpattern.py ================================================ """This file lets you convert image files to knitting patterns. """ import PIL.Image from ..Loader import PathLoader from ..Dumper import JSONDumper from .load_and_dump import decorate_load_and_dump import os @decorate_load_and_dump(PathLoader, JSONDumper) def convert_image_to_knitting_pattern(path, colors=("white", "black")): """Load a image file such as a png bitmap of jpeg file and convert it to a :ref:`knitting pattern file `. :param list colors: a list of strings that should be used as :ref:`colors `. :param str path: ignore this. It is fulfilled by the loeder. Example: .. code:: python convert_image_to_knitting_pattern().path("image.png").path("image.json") """ image = PIL.Image.open(path) pattern_id = os.path.splitext(os.path.basename(path))[0] rows = [] connections = [] pattern_set = { "version": "0.1", "type": "knitting pattern", "comment": { "source": path }, "patterns": [ { "name": pattern_id, "id": pattern_id, "rows": rows, "connections": connections } ]} bbox = image.getbbox() if not bbox: return pattern_set white = image.getpixel((0, 0)) min_x, min_y, max_x, max_y = bbox last_row_y = None for y in reversed(range(min_y, max_y)): instructions = [] row = {"id": y, "instructions": instructions} rows.append(row) for x in range(min_x, max_x): if image.getpixel((x, y)) == white: color = colors[0] else: color = colors[1] instruction = {"color": color} instructions.append(instruction) if last_row_y is not None: connections.append({"from": {"id": last_row_y}, "to": {"id": y}}) last_row_y = y return pattern_set __all__ = ["convert_image_to_knitting_pattern"] ================================================ FILE: knittingpattern/convert/load_and_dump.py ================================================ """convinience methods for conversion Best to use :meth:`decorate_load_and_dump`. """ from functools import wraps def load_and_dump(create_loader, create_dumper, load_and_dump_): """:return: a function that has the doc string of :paramref:`load_and_dump_` additional arguments to this function are passed on to :paramref:`load_and_dump_`. :param create_loader: a loader, e.g. :class:`knittingpattern.Loader.PathLoader` :param create_dumper: a dumper, e.g. :class:`knittingpattern.Dumper.ContentDumper` :param load_and_dump_: a function to call with the loaded content. The arguments to both, :paramref:`create_dumper` and, :paramref:`create_loader` will be passed to :paramref:`load_and_dump_`. Any additional arguments to the return value are also passed to :paramref:`load_and_dump_`. The return value of :paramref:`load_and_dump_` is passed back to the :paramref:`Dumper`. .. seealso:: :func:`decorate_load_and_dump` """ @wraps(load_and_dump_) def load_and_dump__(*args1, **kw): """Return the loader.""" def load(*args2): """Return the dumper.""" def dump(*args3): """Dump the object.""" return load_and_dump_(*(args2 + args3 + args1), **kw) return create_dumper(dump) return create_loader(load) return load_and_dump__ def decorate_load_and_dump(create_loader, create_dumper): """Same as :func:`load_and_dump` but returns a function to enable decorator syntax. Examples: .. code:: Python @decorate_load_and_dump(ContentLoader, JSONDumper) def convert_from_loader_to_dumper(loaded_stuff, other="arguments"): # convert return converted_stuff @decorate_load_and_dump(PathLoader, lambda dump: ContentDumper(dump, encoding=None)) def convert_from_loader_to_dumper(loaded_stuff, to_file): # convert to_file.write(converted_stuff) """ return lambda func: load_and_dump(create_loader, create_dumper, func) __all__ = ["load_and_dump", "decorate_load_and_dump"] ================================================ FILE: knittingpattern/convert/test/test_AYABPNGBuilder.py ================================================ """Test creating png files from knitting patterns. Each pixel is an instruction.""" from test_convert import fixture, pytest, MagicMock, call from knittingpattern.convert.AYABPNGBuilder import AYABPNGBuilder from collections import namedtuple import PIL.Image import tempfile ColorInGrid = namedtuple("ColorInGrid", ["x", "y", "color"]) @fixture def builder(): return AYABPNGBuilder(-1, -1, 10, 5) class TestColorConversion(object): """Convert color names to RGB colors. One could use the webcolors package for that: https://pypi.python.org/pypi/webcolors/ """ @fixture def convert(self): return AYABPNGBuilder._convert_color_to_rrggbb def test_convert_24_bit(self, convert): assert convert("#123456") == "#123456" def test_convert_blue(self, convert): assert convert("blue") == "#0000ff" def test_can_convert_anything_to_color(self, convert): assert convert("ajsdkahsj") != convert("ajsahsj") class TestBounds(object): """Check whether points are inside and outside of the bounds.""" @pytest.mark.parametrize('x, y', [(0, 0), (-1, 0), (0, -1), (0, 4), (9, 4)]) def test_inside(self, builder, x, y): assert builder.is_in_bounds(x, y) @pytest.mark.parametrize('x, y', [(-2, -2), (10, 0), (5, 5), (30, 30), (12, 12)]) def test_outside(self, builder, x, y): assert not builder.is_in_bounds(x, y) class TestSetPixel(object): @fixture def set(self): return MagicMock() @fixture def patched(self, builder, set): builder._set_pixel = set return builder def test_set_pixel(self, patched, set): patched.set_pixel(1, 2, "#aaaaaa") set.assert_called_with(1, 2, "#aaaaaa") def test_set_pixel_converts_color(self, patched, set): patched.set_pixel(2, 3, "black") set.assert_called_with(2, 3, "#000000") def test_set_with_instruction(self, patched, set): patched.set_color_in_grid(ColorInGrid(0, 0, "#adadad")) set.assert_called_with(0, 0, "#adadad") def test_call_many_instructions(self, patched, set): patched.set_colors_in_grid([ ColorInGrid(0, 0, "#000000"), ColorInGrid(0, 1, "#111111"), ColorInGrid(2, 0, "#222222") ]) set.assert_has_calls([call(0, 0, "#000000"), call(0, 1, "#111111"), call(2, 0, "#222222")]) def test_setiing_color_none_does_nothing(self, patched, set): patched.set_pixel(2, 2, None) patched.set_color_in_grid(ColorInGrid(0, 0, None)) patched.set_colors_in_grid([ ColorInGrid(0, 0, None), ColorInGrid(0, 1, None), ColorInGrid(2, 0, None) ]) assert not set.called class TestSavingAsPNG(object): @fixture(scope="class") def image_path(self): return tempfile.mktemp() @fixture(scope="class") def builder(self, image_path): builder = AYABPNGBuilder(-1, -1, 2, 2) # set pixels inside builder.set_pixel(0, 0, "#000000") builder.set_pixel(-1, -1, "#111111") builder.set_pixel(1, 1, "#222222") # set out of bounds pixels builder.set_colors_in_grid([ColorInGrid(12, 12, "red")]) builder.set_color_in_grid(ColorInGrid(-3, -3, "#adadad")) builder.set_pixel(-3, 4, "#adadad") builder.write_to_file(image_path) return builder @fixture(scope="class") def image(self, image_path, builder): return PIL.Image.open(image_path) def test_pixels_are_set(self, image): assert image.getpixel((1, 1)) == (0, 0, 0) assert image.getpixel((0, 0)) == (0x11, 0x11, 0x11) assert image.getpixel((2, 2)) == (0x22, 0x22, 0x22) def test_bbox_is_3x3(self, image): assert image.getbbox() == (0, 0, 3, 3) def test_other_pixels_have_default_color(self, image): assert image.getpixel((1, 2)) == (255, 255, 255) class TestDefaultColor(object): @fixture def default_color(self, builder): return builder.default_color def test_can_change_default_color(self): builder = AYABPNGBuilder(-1, -1, 2, 2, "black") assert builder.default_color == "black" def test_default_color_is_white(self, default_color): assert default_color == "white" ================================================ FILE: knittingpattern/convert/test/test_SVGBuilder.py ================================================ from test_convert import fixture, parse_file, raises from knittingpattern.convert.SVGBuilder import SVGBuilder import io BBOX = (-1, -2, 5, 10) @fixture def file(): return io.StringIO() @fixture def builder(): builder = SVGBuilder() builder.bounding_box = BBOX return builder @fixture def svg(builder, file): def svg(): builder.write_to_file(file) file.seek(0) print(file.read()) file.seek(0) return parse_file(file).svg return svg @fixture def svg1(builder, svg): instruction = "" builder.place(0, 0, instruction.format(1), "row1") builder.place(1, 0, instruction.format(2), "row1") builder.place(2, 0, instruction.format(3), "row1") builder.place(0, 1, instruction.format(4), "row2") builder.place(1, 1, instruction.format(5), "row2") builder.place(2.0, 1.0, instruction.format(6), "row2") return svg() @fixture def row1(svg1): return svg1.g[0] @fixture def row2(svg1): return svg1.g[1] @fixture def instruction1(row1): return row1.g[0] @fixture def instruction2(row1): return row1.g[1] @fixture def instruction3(row1): return row1.g[2] @fixture def instruction21(row2): return row2.g[0] @fixture def instruction22(row2): return row2.g[1] @fixture def instruction23(row2): return row2.g[2] def test_rendering_nothing_is_a_valid_xml(builder, file): builder.write_to_file(file) file.seek(0) first_line = file.readline() assert first_line.endswith("?>\n") assert first_line.startswith("]*>([^<]*)", content)[-1] return content.title.cdata def is_knit(content): return title(content) == "knit" def is_purl(content): return title(content) == "purl" def is_yo(content): return title(content) == "yo" def is_k2tog(content): return title(content) == "k2tog" def is_default(content): return title(content) == "default" def read(path): with open(path) as file: return file.read() file_to_test = { KNIT_FILE: is_knit, PURL_FILE: is_purl, YO_FILE: is_yo, K2TOG_FILE: is_k2tog, DEFAULT_FILE: is_default } @pytest.mark.parametrize('path, test', list(file_to_test.items())) def test_tests_work_on_corresponding_file(path, test): assert test(read(path)) @pytest.mark.parametrize('path, test', [ (path, _test) for path in file_to_test for test_path, _test in file_to_test.items() if path != test_path ]) def test_tests_do_not_work_on_other_files(path, test): assert not test(read(path)) def test_default_content_has_identifier_in_place(): assert "{instruction.type}" in read(DEFAULT_FILE) __all__ = [ "KNIT_FILE", "PURL_FILE", "YO_FILE", "K2TOG_FILE", "IMAGES_FOLDER", "IMAGES_FOLDER_NAME", "DEFAULT_FILE", "read", "title", "is_knit", "is_purl", "is_yo", "is_k2tog", "is_default", ] ================================================ FILE: knittingpattern/convert/test/test_instruction_to_svg.py ================================================ from test_images import IMAGES_FOLDER, DEFAULT_FILE,\ IMAGES_FOLDER_NAME, is_knit, is_purl from test_convert import fixture, parse_string from knittingpattern.convert.InstructionToSVG import InstructionToSVG from collections import namedtuple Instruction = namedtuple("TestInstruction", ["type", "hex_color"]) XML_START = '\n' @fixture def knit(): return Instruction("knit", "green") @fixture def purl(): return Instruction("purl", "red") @fixture def yo(): return Instruction("yo", "brown") @fixture def its(): return InstructionToSVG() @fixture def loaded(its): its.load.folder(IMAGES_FOLDER) return its @fixture def default(its): its.load.path(DEFAULT_FILE) return its class TestHasSVGForInstruction(object): """This tests the `InstructionToSVG.has_instruction_to_svg` method.""" def test_load_from_file(self, its, knit): its.load.relative_file(__name__, IMAGES_FOLDER_NAME + "/knit.svg") assert its.has_svg_for_instruction(knit) def test_nothing_is_loaded(self, its, knit, purl): assert not its.has_svg_for_instruction(knit) assert not its.has_svg_for_instruction(purl) def test_load_from_directory(self, its, knit, purl): its.load.relative_folder(__name__, IMAGES_FOLDER_NAME) assert its.has_svg_for_instruction(knit) assert its.has_svg_for_instruction(purl) def test_all_images_are_loaded(self, loaded, knit, purl, yo): assert loaded.has_svg_for_instruction(knit) assert loaded.has_svg_for_instruction(purl) assert loaded.has_svg_for_instruction(yo) def test_default_returns_empty_string_if_nothing_is_loaded(self, its, knit): assert its.default_instruction_to_svg(knit) == XML_START class TestDefaultInstrucionToSVG(object): """This tests the `InstructionToSVG.default_instruction_to_svg` method.""" def test_instruction_type_is_replaced_in_default(self, default, knit): assert "knit" in default.instruction_to_svg(knit) def test_default_instruction_is_used(self, default, purl): default_string = default.default_instruction_to_svg(purl) string = default.instruction_to_svg(purl) assert string == default_string def is_color_layer(layer): layer_has_label_color = layer["inkscape:label"] == "color" layer_is_of_mode_layer = layer["inkscape:groupmode"] == "layer" return layer_has_label_color and layer_is_of_mode_layer def color_layers(svg): return [layer for layer in svg.g if is_color_layer(layer)] def assert_has_one_colored_layer(svg): assert len(color_layers(svg)) == 1 def assert_fill_has_color_of(svg, instruction): colored_layer = color_layers(svg)[0] element = (colored_layer.rect if "rect" in dir(colored_layer) else colored_layer.circle) style = element["style"] assert "fill:" + instruction.hex_color in style class TestInstructionToSVG(object): @fixture def knit_svg(self, loaded, knit): return parse_string(loaded.instruction_to_svg(knit)).svg @fixture def purl_svg(self, loaded, purl): return parse_string(loaded.instruction_to_svg(purl)).svg def test_file_content_is_included(self, knit_svg): assert is_knit(knit_svg) def test_file_content_is_purl(self, purl_svg): assert is_purl(purl_svg) def test_returned_object_is_svg_with_viewbox(self, knit_svg): assert len(knit_svg["viewBox"].split()) == 4 def test_there_is_one_color_layer(self, knit_svg): assert_has_one_colored_layer(knit_svg) def test_purl_has_one_color_layer(self, purl_svg): assert_has_one_colored_layer(purl_svg) def test_fill_in_colored_layer_is_replaced_by_color(self, knit_svg, knit): assert_fill_has_color_of(knit_svg, knit) def test_purl_is_colored(self, purl_svg, purl): assert_fill_has_color_of(purl_svg, purl) # TODO: test colored layer so it does everything as specified ================================================ FILE: knittingpattern/convert/test/test_knittingpattern_to_png.py ================================================ from test_convert import fixture, pytest from knittingpattern import load_from_relative_file import PIL.Image @fixture(scope="module") def block4x4(): return load_from_relative_file(__name__, "test_patterns/block4x4.json") @fixture(scope="module") def path(block4x4): return block4x4.to_ayabpng().temporary_path() @fixture(scope="module") def image(path): return PIL.Image.open(path) @pytest.mark.parametrize('xy', [(i, i) for i in range(4)]) def test_there_is_a_green_line(image, xy): assert image.getpixel(xy) == (0, 128, 0) def test_path_ends_with_png(path): assert path.endswith(".png") ================================================ FILE: knittingpattern/convert/test/test_layout.py ================================================ """Test the layout of knitting patterns.""" from test_convert import fixture import os from knittingpattern.convert.Layout import GridLayout, InstructionInGrid from knittingpattern import load_from_relative_file from collections import namedtuple def coordinates(layout): """The coordinates of the layout.""" return list(layout.walk_instructions(lambda point: (point.x, point.y))) def sizes(layout): """The sizes of the instructions of the layout.""" return list(layout.walk_instructions(lambda p: (p.width, p.height))) def instructions(layout): """The instructions of the layout.""" return list(layout.walk_instructions(lambda point: point.instruction)) def row_ids(layout): """The ids of the rows of the layout.""" return list(layout.walk_rows(lambda row: row.id)) def connections(layout): """The connections between the rows of the leyout.""" return list(layout.walk_connections(lambda c: (c.start.xy, c.stop.xy))) class BaseTest: """Base class for a set of tests on knitting patterns.""" FILE = "block4x4.json" PATTERN = "knit" COORDINATES = [(x, y) for y in range(4) for x in range(4)] SIZES = [(1, 1)] * 16 ROW_IDS = [1, 2, 3, 4] LARGER_CONNECTIONS = [] BOUNDING_BOX = (0, 0, 4, 4) @fixture(scope="class") def pattern(self): """Teh pattern to test.""" path = os.path.join("test_patterns", self.FILE) pattern_set = load_from_relative_file(__name__, path) return pattern_set.patterns[self.PATTERN] @fixture(scope="class") def grid(self, pattern): """The computed grid for the pattern.""" return GridLayout(pattern) def test_coordinates(self, grid): """Test the coordinates of the layout.""" coords = coordinates(grid) print("generated:", coords) print("expected: ", self.COORDINATES) assert coords == self.COORDINATES def test_size(self, grid): """Test teh sizes of the layout.""" generated = sizes(grid) print("generated:", generated) print("expected: ", self.SIZES) assert generated == self.SIZES def test_instructions(self, grid, pattern): """Test that the instructions are returned row-wise.""" instructions_ = [] for row_id in self.ROW_IDS: for instruction in pattern.rows[row_id].instructions: instructions_.append(instruction) assert instructions(grid) == instructions_ def test_row_ids(self, grid): """Test the order of rows.""" assert row_ids(grid) == self.ROW_IDS def test_connections(self, grid): """test the connections betwen rows.""" generated = connections(grid) print("generated:", generated) print("expected: ", self.LARGER_CONNECTIONS) assert generated == self.LARGER_CONNECTIONS def test_bounding_box(self, grid): """Test the bounding box of the layout.""" assert grid.bounding_box == self.BOUNDING_BOX class TestBlock4x4(BaseTest): """Execute the BaseTest.""" class TestHole(BaseTest): """Test if holes are layouted.""" FILE = "with hole.json" SIZES = BaseTest.SIZES[:] SIZES[5] = (2, 1) SIZES[6] = (0, 1) COORDINATES = BaseTest.COORDINATES[:] COORDINATES[6] = COORDINATES[7] class TestAddAndRemoveMeshes(BaseTest): """take away and add meshes at the right and left.""" FILE = "add and remove meshes.json" SIZES = [(1, 1)] * 17 COORDINATES = [ (0, 0), (1, 0), (2, 0), (3, 0), (0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (0, 2), (1, 2), (2, 2), (-1, 3), (0, 3), (1, 3), (2, 3), (3, 3) ] BOUNDING_BOX = (-1, 0, 5, 4) # test how instructions are connected @fixture def i_1(self, pattern): return pattern.rows[1].instructions @fixture def i_2(self, pattern): return pattern.rows[2].instructions @fixture def i_3(self, pattern): return pattern.rows[3].instructions @fixture def i_4(self, pattern): return pattern.rows[4].instructions @fixture def instructions(self, i_1, i_2, i_3, i_4): return i_1 + i_2 + i_3 + i_4 def test_all_consume_one_mesh(self, instructions): assert all(i.number_of_consumed_meshes == 1 for i in instructions) def test_all_produce_one_mesh(self, instructions): assert all(i.number_of_produced_meshes == 1 for i in instructions) # i_1 produced def test_i_1_0_is_not_produced(self, i_1): assert i_1[0].producing_instructions == [None] def test_i_1_1_is_not_produced(self, i_1): assert i_1[1].producing_instructions == [None] def test_i_1_2_is_not_produced(self, i_1): assert i_1[2].producing_instructions == [None] def test_i_1_3_is_not_produced(self, i_1): assert i_1[3].producing_instructions == [None] # i_1 consumed def test_i_1_0_consumed(self, i_1, i_2): assert i_1[0].consuming_instructions == [i_2[0]] def test_i_1_1_consumed(self, i_1, i_2): assert i_1[1].consuming_instructions == [i_2[1]] def test_i_1_2_consumed(self, i_1, i_2): assert i_1[2].consuming_instructions == [i_2[2]] def test_i_1_3_consumed(self, i_1, i_2): assert i_1[3].consuming_instructions == [i_2[3]] # i_2 produced def test_i_2_0_produced(self, i_1, i_2): assert i_2[0].producing_instructions == [i_1[0]] def test_i_2_1_produced(self, i_1, i_2): assert i_2[1].producing_instructions == [i_1[1]] def test_i_2_2_produced(self, i_1, i_2): assert i_2[2].producing_instructions == [i_1[2]] def test_i_2_3_produced(self, i_1, i_2): assert i_2[3].producing_instructions == [i_1[3]] def test_i_2_4_produced(self, i_2): assert i_2[4].producing_instructions == [None] # i_2 consumed def test_i_2_0_consumed(self, i_2, i_3): assert i_2[0].consuming_instructions == [i_3[0]] def test_i_2_1_consumed(self, i_2, i_3): assert i_2[1].consuming_instructions == [i_3[1]] def test_i_2_2_consumed(self, i_2, i_3): assert i_2[2].consuming_instructions == [i_3[2]] def test_i_2_3_not_consumed(self, i_2): assert i_2[3].consuming_instructions == [None] def test_i_2_4_not_consumed(self, i_2): assert i_2[4].consuming_instructions == [None] # i_3 produced def test_i_3_0_produced(self, i_2, i_3): assert i_3[0].producing_instructions == [i_2[0]] def test_i_3_1_produced(self, i_2, i_3): assert i_3[1].producing_instructions == [i_2[1]] def test_i_3_2_produced(self, i_2, i_3): assert i_3[2].producing_instructions == [i_2[2]] # i_3 consumed def test_i_3_0_consumed(self, i_3, i_4): assert i_3[0].consuming_instructions == [i_4[1]] def test_i_3_1_consumed(self, i_3, i_4): assert i_3[1].consuming_instructions == [i_4[2]] def test_i_3_2_consumed(self, i_3, i_4): assert i_3[2].consuming_instructions == [i_4[3]] # i_4 produced def test_i_4_0_not_produced(self, i_4): assert i_4[0].producing_instructions == [None] def test_i_4_1_produced(self, i_3, i_4): assert i_4[1].producing_instructions == [i_3[0]] def test_i_4_2_produced(self, i_3, i_4): assert i_4[2].producing_instructions == [i_3[1]] def test_i_4_3_produced(self, i_3, i_4): assert i_4[3].producing_instructions == [i_3[2]] def test_i_4_4_not_produced(self, i_4): assert i_4[4].producing_instructions == [None] # i_4 consumed def test_i_4_0_not_consumed(self, i_4): assert i_4[0].consuming_instructions == [None] def test_i_4_1_not_consumed(self, i_4): assert i_4[1].consuming_instructions == [None] def test_i_4_2_not_consumed(self, i_4): assert i_4[2].consuming_instructions == [None] def test_i_4_3_not_consumed(self, i_4): assert i_4[3].consuming_instructions == [None] def test_i_4_4_not_consumed(self, i_4): assert i_4[4].consuming_instructions == [None] class TestParallelRows(BaseTest): """Test unconnected rows of different sizes.""" FILE = "split_up_and_add_rows.json" SIZES = [(1, 1)] * 15 SIZES[-2] = (2, 1) COORDINATES = [ (0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (3, 1), (4, 1), (0, 2), (1, 2), # could also be (0, 1), (1, 1) (3, 2), (4, 2), (0, 3), (1, 3), (2, 3), (4, 3) ] ROW_IDS = ["1.1", "2.2", "2.1", "3.2", "4.1"] # LARGER_CONNECTIONS = [((0, 1), (0, 3)), ((1, 1), (1, 3))] LARGER_CONNECTIONS = [((0, 0), (0, 2)), ((1, 0), (1, 2))] BOUNDING_BOX = (0, 0, 5, 4) @fixture def row_4(self, pattern): return pattern.rows["4.1"] @fixture def skp(self, row_4): return row_4.instructions[2] def test_skp_has_2_consumed_meshes(self, skp): """Test skp consumes two meshes.""" assert skp.type == "skp" assert skp.number_of_consumed_meshes == 2 def test_row_4_1_consumes_5_meshes(self, row_4): """Test: the yo in the last row adds to the skp so the width is 5.""" assert row_4.number_of_consumed_meshes == 5 assert len(row_4.consumed_meshes) == 5 Instruction = namedtuple("Instruction", ["color", "number_of_consumed_meshes"]) def test_get_color_from_instruction_in_grid(): """Test the color attribute.""" instruction = Instruction("black", 1) instruction_in_grid = InstructionInGrid(instruction, (0, 0)) assert instruction_in_grid.color == "black" class TestSmallCafe(BaseTest): """This test tests the negative expansion of rows. If you start expanding in the middle, this tests that all rows are included. """ FILE = "small-cafe.json" PATTERN = "A.2" SIZES = \ [(1, 1)] * 12 + \ [(0, 1), (1, 1), (0, 1)] + [(1, 1)] * 11 + \ [(1, 1)] * 14 + \ [(1, 1)] * 3 + [(0, 1)] + [(1, 1)] * 11 + [(0, 1)] + \ [(1, 1)] * 16 COORDINATES = \ [(i, -1) for i in range(12)] + \ [(0, 0), (0, 0), (1, 0)] + [(i, 0) for i in range(1, 12)] + \ [(i, 1) for i in range(-1, 13)] + \ [(-1, 2), (0, 2), (1, 2), (2, 2)] + [(i, 2) for i in range(2, 13)] + \ [(13, 2)] + \ [(i, 3) for i in range(-2, 14)] ROW_IDS = ["B.first", "A.2.25", "A.2.26", "A.2.27", "A.2.28"] LARGER_CONNECTIONS = [] BOUNDING_BOX = (-2, -1, 15, 4) class TestCastOffAndBindOn(BaseTest): """Cast On and Bind off have no size but must be layouted differently.""" FILE = "cast_on_and_bind_off.json" SIZES = [(1, 1)] * 12 COORDINATES = [(x, y) for y in range(3) for x in range(4)] ROW_IDS = [1, 2, 3] LARGER_CONNECTIONS = [] BOUNDING_BOX = (0, 0, 4, 3) @fixture def cast_on(self, co_row): return co_row.instructions[0] @fixture def co_row(self, pattern): return pattern.rows[1] @fixture def co_row_in_grid(self, grid, co_row): return grid.row_in_grid(co_row) def test_cast_on_has_layout_specific_width(self, cast_on): """Test that cast On has a custom width.""" assert cast_on["grid-layout"]["width"] == 1 def test_first_row_has_width_4(self, co_row_in_grid): """Test that the Cast On row has a width.""" assert co_row_in_grid.width == 4 # TODO # # def test_use_row_with_lowest_number_of_incoming_connections_as_first_row(): # fail() # # # def test_if_row_with_lowest_number_of_connections_exist_use_smallest_id(): # fail() ================================================ FILE: knittingpattern/convert/test/test_patterns/add and remove meshes.json ================================================ { "type" : "knitting pattern", "version" : "0.1", "comment" : { "content" : "4x4 meshes block", "type" : "markdown" }, "patterns" : [ { "id" : "knit", "name" : "add and remove meshes", "rows" : [ { "id" : 1, "instructions" : [ {"id": "1.0"}, {"id": "1.1"}, {"id": "1.2"}, {"id": "1.3"} ] }, { "id" : 2, "instructions" : [ {"id": "2.0"}, {"id": "2.1"}, {"id": "2.2"}, {"id": "2.3"}, {"id": "2.5"} ] }, { "id" : 3, "instructions" : [ {"id": "3.0"}, {"id": "3.1"}, {"id": "3.2"} ] }, { "id" : 4, "instructions" : [ {"id": "4.-1"}, {"id": "4.0"}, {"id": "4.1"}, {"id": "4.2"}, {"id": "4.3"} ] } ], "connections" : [ { "from" : { "id" : 1 }, "to" : { "id" : 2 } }, { "from" : { "id" : 2 }, "to" : { "id" : 3 } }, { "from" : { "id" : 3 }, "to" : { "start" : 1, "id" : 4 } } ] } ] } ================================================ FILE: knittingpattern/convert/test/test_patterns/block4x4.json ================================================ { "type" : "knitting pattern", "version" : "0.1", "comment" : { "content" : "4x4 meshes block", "type" : "markdown" }, "patterns" : [ { "id" : "knit", "name" : "knit 4x4", "rows" : [ { "id" : 1, "instructions" : [ {"id": "1.0", "color": "green"}, {"id": "1.1"}, {"id": "1.2"}, {"id": "1.3"} ] }, { "id" : 3, "instructions" : [ {"id": "3.0"}, {"id": "3.1"}, {"id": "3.2", "color": "green"}, {"id": "3.3"} ] }, { "id" : 2, "instructions" : [ {"id": "2.0"}, {"id": "2.1", "color": "green"}, {"id": "2.2"}, {"id": "2.3"} ] }, { "id" : 4, "instructions" : [ {"id": "4.0"}, {"id": "4.1"}, {"id": "4.2"}, {"id": "4.3", "color": "green"} ] } ], "connections" : [ { "from" : { "id" : 1 }, "to" : { "id" : 2 } }, { "from" : { "id" : 2 }, "to" : { "id" : 3 } }, { "from" : { "id" : 3 }, "to" : { "id" : 4 } } ] } ] } ================================================ FILE: knittingpattern/convert/test/test_patterns/cast_on_and_bind_off.json ================================================ { "type" : "knitting pattern", "version" : "0.1", "comment" : { "content" : "cast on and bind off", "type" : "markdown" }, "patterns" : [ { "id" : "knit", "name" : "cobo", "rows" : [ { "id" : 1, "instructions" : [ {"id": "1.0", "type": "co"}, {"id": "1.1", "type": "co"}, {"id": "1.2", "type": "co"}, {"id": "1.3", "type": "co"} ] }, { "id" : 2, "instructions" : [ {"id": "3.0"}, {"id": "3.1"}, {"id": "3.2"}, {"id": "3.3"} ] }, { "id" : 3, "instructions" : [ {"id": "2.0", "type": "bo"}, {"id": "2.1", "type": "bo"}, {"id": "2.2", "type": "bo"}, {"id": "2.3", "type": "bo"} ] } ], "connections" : [ { "from" : { "id" : 1 }, "to" : { "id" : 2 } }, { "from" : { "id" : 2 }, "to" : { "id" : 3 } } ] } ] } ================================================ FILE: knittingpattern/convert/test/test_patterns/small-cafe.json ================================================ { "version" : "0.1", "type" : "knitting pattern", "comment" : { "markdown" : "This pattern is taken from [garnstudio](http://www.garnstudio.com/pattern.php?id=7350&cid=19). It only contains the last 4 rows", "picture" : { "type" : "url", "url" : "http://www.garnstudio.com/drops/mag/167/17/17-lp.jpg" } }, "patterns" : [ { "name" : "A.2", "id" : "A.2", "rows" : [ { "id" : "A.2.25", "color" : "light brown", "instructions" : [ { "type" : "yo" }, {}, { "type" : "yo" }, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} ] }, { "id" : "A.2.26", "color" : "light brown", "instructions" : [ { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" } ] }, { "id" : "A.2.27", "color" : "light brown", "instructions" : [ {}, {}, {}, { "type" : "yo" }, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, { "type" : "yo" } ] }, { "id" : "A.2.28", "color" : "light brown", "instructions" : [ { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" } ] }, { "id" : "B.first", "color" : "light brown", "instructions" : [ {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} ] } ], "connections" : [ { "from" : { "id" : "B.first" }, "to" : { "id" : "A.2.25" } }, { "from" : { "id" : "A.2.25" }, "to" : { "id" : "A.2.26", "start" : 1 } }, { "from" : { "id" : "A.2.26" }, "to" : { "id" : "A.2.27" } }, { "from" : { "id" : "A.2.27" }, "to" : { "id" : "A.2.28", "start" : 1 } } ] } ] } ================================================ FILE: knittingpattern/convert/test/test_patterns/split_up_and_add_rows.json ================================================ { "type" : "knitting pattern", "version" : "0.1", "patterns" : [ { "id" : "knit", "name" : "A.1", "rows" : [ { "id" : "1.1", "instructions" : [ {"id": "1.1.0"}, {"id": "1.1.1"}, {"id": "1.1.2"}, {"id": "1.1.3"}, {"id": "1.1.4"} ] }, { "id" : "2.1", "instructions" : [ {"id": "2.1.0"}, {"id": "2.1.1"} ] }, { "id" : "2.2", "instructions" : [ {"id": "2.2.0"}, {"id": "2.2.1"} ] }, { "id" : "3.2", "instructions" : [ {"id": "3.2.0"}, {"id": "3.2.1"} ] }, { "id" : "4.1", "instructions" : [ {"id": "4.1.0"}, {"id": "4.1.1"}, {"id": "4.1.2", "type": "skp"}, {"id": "4.1.4"} ] } ], "connections" : [ { "from" : { "id" : "1.1", "start" : 0 }, "to" : { "id" : "2.1", "start" : 0 }, "meshes" : 2 }, { "from" : { "id" : "1.1", "start" : 3 }, "to" : { "id" : "2.2", "start" : 0 }, "meshes" : 2 }, { "from" : { "id" : "2.2", "start" : 0 }, "to" : { "id" : "3.2", "start" : 0 } }, { "from" : { "id" : "2.1" }, "to" : { "id" : "4.1" } }, { "from" : { "id" : "3.2", "start" : 0 }, "to" : { "id" : "4.1", "start" : 3 } } ] } ] } ================================================ FILE: knittingpattern/convert/test/test_patterns/with hole.json ================================================ { "type" : "knitting pattern", "version" : "0.1", "comment" : { "content" : "4x4 meshes block", "type" : "markdown" }, "patterns" : [ { "id" : "knit", "name" : "knit 4x4", "rows" : [ { "id" : 1, "instructions" : [ {"id": "1.0"}, {"id": "1.1"}, {"id": "1.2"}, {"id": "1.3"} ] }, { "id" : 2, "instructions" : [ {"id": "2.0"}, {"id": "2.1", "type": "skp"}, {"id": "2.2", "type": "yo"}, {"id": "2.3"} ] }, { "id" : 3, "instructions" : [ {"id": "3.0"}, {"id": "3.1"}, {"id": "3.2"}, {"id": "3.3"} ] }, { "id" : 4, "instructions" : [ {"id": "4.0"}, {"id": "4.1"}, {"id": "4.2"}, {"id": "4.3"} ] } ], "connections" : [ { "from" : { "id" : 1 }, "to" : { "id" : 2 } }, { "from" : { "id" : 2 }, "to" : { "id" : 3 } }, { "from" : { "id" : 3 }, "to" : { "id" : 4 } } ] } ] } ================================================ FILE: knittingpattern/convert/test/test_png_to_knittingpattern.py ================================================ from test_convert import fixture, HERE, os from knittingpattern.convert.image_to_knittingpattern import \ convert_image_to_knitting_pattern from knittingpattern import convert_from_image from PIL import Image IMAGE_PATH = os.path.join(HERE, "pictures") @fixture(scope="module") def patterns(image_path, convert): loader = convert() return loader.path(image_path).knitting_pattern() @fixture(scope="module") def pattern(patterns): return patterns.patterns.at(0) @fixture(scope="module") def image(image_path): return Image.open(image_path) def pytest_generate_tests(metafunc): if "image_path" in metafunc.fixturenames: metafunc.parametrize("image_path", [ os.path.join(IMAGE_PATH, file) for file in os.listdir(IMAGE_PATH) if file.startswith("conversion")], scope="module") if "convert" in metafunc.fixturenames: metafunc.parametrize("convert", [convert_image_to_knitting_pattern, convert_from_image], scope="module") def test_convert_image_to_knittingpattern(patterns, image_path): assert patterns.comment["source"] == image_path def test_row_length_is_image_length(pattern, image): min_x, min_y, max_x, max_y = image.getbbox() assert len(pattern.rows.at(0).instructions) == max_x - min_x def test_first_color_is_white(pattern): assert pattern.rows[0].instructions[0].color == "white" def test_other_color_is_white(pattern): assert pattern.rows[1].instructions[1].color == "white" def test_black_exists(pattern): assert pattern.rows[20].instructions[64].color == "black" def test_order_of_conversion(): loader = convert_from_image() dumper = loader.relative_file(HERE, "pictures/color-order.png") patterns = dumper.knitting_pattern() pattern = patterns.patterns.at(0) row1, row2, row3 = pattern.rows assert row1.first_instruction.color == row2.first_instruction.color assert row2.first_instruction.color != row3.first_instruction.color ================================================ FILE: knittingpattern/convert/test/test_save_as_svg.py ================================================ from test_convert import fixture from knittingpattern import load_from_relative_file import untangle from itertools import chain import re INKSCAPE_MESSAGE = "row is usable by inkscape" TRANSFORM_REGEX = "^translate\(\s*(\S+?)\s*,\s*(\S+?)\s*\)\s*,"\ "\s*scale\(\s*(\S+?)\s*\)$" ZOOM_MESSAGE = "zoom is computed from height" DEFAULT_ZOOM = 25 def is_close_to(v1, v2, relative_epsilon=0.01): return v2 * (1 - relative_epsilon) < v1 < v2 * (1 + relative_epsilon) @fixture(scope="module") def patterns_svg(): return load_from_relative_file(__name__, "test_patterns/block4x4.json") @fixture(scope="module") def path(patterns_svg): return patterns_svg.to_svg(zoom=DEFAULT_ZOOM).temporary_path(".svg") @fixture(scope="module") def svg(path): return untangle.parse(path).svg @fixture def rows(svg): return [("row-{}".format(i), row) for i, row in enumerate(svg.g, 1)] @fixture def instructions(rows): return chain(*(row.g for _, row in rows)) def rows_test(function): def test(rows): for row_id, row in rows: function(row_id, row) return test def instructions_test(function): def test(instructions): for instruction in instructions: function(instruction) return test def instructions_svg_test(function): def test(instructions): for instruction in instructions: function(instruction, svg, path, patterns_svg) return test def test_svg_contains_four_rows(svg): assert len(svg.g) == 4 @rows_test def test_rows_contain_four_instructions(row_id, row): assert len(row.g) == 4 @rows_test def test_rows_are_labeled_for_inkscape(row_id, row): assert row["inkscape:label"] == row_id @rows_test def test_row_is_inkscape_layer(row_id, row): assert row["inkscape:groupmode"] == "layer" @rows_test def test_rows_have_class_for_styling(row_id, row): assert row["class"] == "row" @rows_test def test_rows_have_id_for_styling(row_id, row): assert row["id"] == row_id @instructions_test def test_instructions_have_class(instruction): assert instruction["class"] == "instruction" @instructions_test def test_instructions_have_id(instruction): # TODO all ids should be made up from the real ids assert instruction["id"].startswith("instruction-") @instructions_test def test_instructions_content_is_knit_svg_file(instruction): assert instruction.use["xlink:href"].startswith("#knit") @instructions_svg_test def test_instructions_have_transform(instruction, svg, path, patterns_svg): transform = instruction["transform"] x, y, zoom = map(float, re.match(TRANSFORM_REGEX, transform).groups()) bbox = list(map(float, svg(path(patterns_svg()))["viewBox"].split())) assert is_close_to(DEFAULT_ZOOM / (bbox[3] - bbox[1]), zoom), ZOOM_MESSAGE ================================================ FILE: knittingpattern/examples/Cafe.json ================================================ { "version" : "0.1", "type" : "knitting pattern", "comment" : { "markdown" : "This pattern is taken from [garnstudio](http://www.garnstudio.com/pattern.php?id=7350&cid=19).", "picture" : { "type" : "url", "url" : "http://www.garnstudio.com/drops/mag/167/17/17-lp.jpg" } }, "patterns" : [ { "name" : "A.2", "id" : "A.2", "rows" : [ { "id" : "A.2.1", "color" : "mocha latte", "instructions" : [ {}, { "type" : "yo" }, {}, {}, {}, {}, { "type" : "cdd" }, {}, {}, {}, {}, { "type" : "yo" } ] }, { "id" : "A.2.2", "color" : "mocha latte", "instructions" : [ {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} ] }, { "id" : "A.2.3", "color" : "dark brown", "instructions" : [ {}, {}, { "type" : "yo" }, {}, {}, {}, { "type" : "cdd" }, {}, {}, {}, { "type" : "yo" }, {} ] }, { "id" : "A.2.4", "color" : "dark brown", "same as" : "A.2.2" }, { "id" : "A.2.5", "color" : "dark brown", "instructions" : [ {}, {}, {}, { "type" : "yo" }, {}, {}, { "type" : "cdd" }, {}, {}, { "type" : "yo" }, {}, {} ] }, { "id" : "A.2.6", "color" : "dark brown", "same as" : "A.2.2" }, { "id" : "A.2.7", "color" : "brown", "instructions" : [ {}, {}, {}, {}, { "type" : "yo" }, {}, { "type" : "cdd" }, {}, { "type" : "yo" }, {}, {}, {} ] }, { "id" : "A.2.8", "color" : "brown", "same as" : "A.2.2" }, { "id" : "A.2.9", "color" : "brown", "instructions" : [ {}, {}, {}, {}, {}, { "type" : "yo" }, { "type" : "cdd" }, { "type" : "yo" }, {}, {}, {}, {} ] }, { "id" : "A.2.10", "color" : "brown", "same as" : "A.2.2" }, { "id" : "A.2.11", "same as" : "A.2.10" }, { "id" : "A.2.12", "color" : "brown", "instructions" : [ { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" } ] }, { "id" : "A.2.13", "color" : "dark brown", "same as" : "A.2.2" }, { "id" : "A.2.14", "color" : "dark brown", "same as" : "A.2.12" }, { "id" : "A.2.15", "color" : "dark brown", "same as" : "A.2.1" }, { "id" : "A.2.16", "color" : "dark brown", "same as" : "A.2.2" }, { "id" : "A.2.17", "color" : "white", "same as" : "A.2.3" }, { "id" : "A.2.18", "color" : "white", "same as" : "A.2.2" }, { "id" : "A.2.19", "color" : "white", "same as" : "A.2.5" }, { "id" : "A.2.20", "color" : "white", "same as" : "A.2.2" }, { "id" : "A.2.21", "color" : "dark brown", "same as" : "A.2.7" }, { "id" : "A.2.22", "color" : "dark brown", "same as" : "A.2.2" }, { "id" : "A.2.23", "color" : "dark brown", "same as" : "A.2.9" }, { "id" : "A.2.24", "color" : "dark brown", "same as" : "A.2.2" }, { "id" : "A.2.25", "color" : "mocha latte", "instructions" : [ { "type" : "yo" }, {}, { "type" : "yo" }, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} ] }, { "id" : "A.2.26", "color" : "mocha latte", "instructions" : [ { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" } ] }, { "id" : "A.2.27", "color" : "mocha latte", "instructions" : [ {}, {}, {}, { "type" : "yo" }, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, { "type" : "yo" } ] }, { "id" : "A.2.28", "color" : "mocha latte", "instructions" : [ { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" }, { "type" : "purl" } ] } ], "connections" : [ { "from" : { "id" : "A.2.1" }, "to" : { "id" : "A.2.2" } }, { "from" : { "id" : "A.2.2" }, "to" : { "id" : "A.2.3" } }, { "from" : { "id" : "A.2.3" }, "to" : { "id" : "A.2.4" } }, { "from" : { "id" : "A.2.4" }, "to" : { "id" : "A.2.5" } }, { "from" : { "id" : "A.2.5" }, "to" : { "id" : "A.2.6" } }, { "from" : { "id" : "A.2.6" }, "to" : { "id" : "A.2.7" } }, { "from" : { "id" : "A.2.7" }, "to" : { "id" : "A.2.8" } }, { "from" : { "id" : "A.2.8" }, "to" : { "id" : "A.2.9" } }, { "from" : { "id" : "A.2.9" }, "to" : { "id" : "A.2.10" } }, { "from" : { "id" : "A.2.10" }, "to" : { "id" : "A.2.11" } }, { "from" : { "id" : "A.2.11" }, "to" : { "id" : "A.2.12" } }, { "from" : { "id" : "A.2.12" }, "to" : { "id" : "A.2.13" } }, { "from" : { "id" : "A.2.13" }, "to" : { "id" : "A.2.14" } }, { "from" : { "id" : "A.2.14" }, "to" : { "id" : "A.2.15" } }, { "from" : { "id" : "A.2.15" }, "to" : { "id" : "A.2.16" } }, { "from" : { "id" : "A.2.16" }, "to" : { "id" : "A.2.17" } }, { "from" : { "id" : "A.2.17" }, "to" : { "id" : "A.2.18" } }, { "from" : { "id" : "A.2.18" }, "to" : { "id" : "A.2.19" } }, { "from" : { "id" : "A.2.19" }, "to" : { "id" : "A.2.20" } }, { "from" : { "id" : "A.2.20" }, "to" : { "id" : "A.2.21" } }, { "from" : { "id" : "A.2.21" }, "to" : { "id" : "A.2.22" } }, { "from" : { "id" : "A.2.22" }, "to" : { "id" : "A.2.23" } }, { "from" : { "id" : "A.2.23" }, "to" : { "id" : "A.2.24" } }, { "from" : { "id" : "A.2.24" }, "to" : { "id" : "A.2.25" } }, { "from" : { "id" : "A.2.25" }, "to" : { "id" : "A.2.26", "start" : 1 } }, { "from" : { "id" : "A.2.26" }, "to" : { "id" : "A.2.27" } }, { "from" : { "id" : "A.2.27" }, "to" : { "id" : "A.2.28", "start" : 1 } } ] } ] } ================================================ FILE: knittingpattern/examples/Charlotte.json ================================================ { "type" : "knitting pattern", "version" : "0.1", "comment" : { "content" : "This knitting pattern was taken from [Garnstudio](http://www.garnstudio.com/pattern.php?id=7342&cid=9).", "type" : "markdown", "picture" : { "url" : "http://www.garnstudio.com/drops/mag/168/15/15-lp.jpg" } }, "patterns" : [ { "id" : "A.1", "name" : "A.1", "rows" : [ { "id" : ["A.1", "empty", "1"], "instructions" : [ {}, {}, {}, {}, {} ] }, { "id" : ["A.1", "empty", "2"], "same as" : ["A.1", "empty", "1"] }, { "id" : ["A.1", "lace", "1"], "instructions" : [ { "type" : "skp" }, { "type" : "yo" }, {}, { "type" : "yo" }, { "type" : "ktog tbl" } ] } ], "connections" : [ { "from" : { "id" : ["A.1", "empty", "1"] }, "to" : { "id" : ["A.1", "empty", "2"] } }, { "from" : { "id" : ["A.1", "empty", "2"] }, "to" : { "id" : ["A.1", "lace", "1"] } } ] }, { "id" : "A.2", "name" : "A.2", "rows" : [ { "id" : ["A.2", "empty", "1"], "instructions" : [ {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} ] }, { "id" : ["A.2", "empty", "2"], "instructions" : [ {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} ] }, { "id" : ["A.2", "lace", "1"], "instructions" : [ { "type" : "skp", "count" : 4 }, { "type" : "yo" }, {}, { "type" : "yo" }, {}, { "type" : "yo" }, {}, { "type" : "yo" }, {}, { "type" : "yo" }, {}, { "type" : "yo" }, { "type" : "ktog tbl", "count" : 4 } ] } ], "connections" : [ { "from" : { "id" : ["A.2", "empty", "1"] }, "to" : { "id" : ["A.2", "empty", "2"] } }, { "from" : { "id" : ["A.2", "empty", "2"] }, "to" : { "id" : ["A.2", "lace", "1"] } } ] } ] } ================================================ FILE: knittingpattern/examples/README.md ================================================ Examples -------- This folder contains the pattern that are useful to see how to specify the format. Also, have a look at - [![](http://www.garnstudio.com/drops/mag/166/20/20-2.jpg)](http://www.garnstudio.com/pattern.php?id=7077&cid=9) - bindoff - knit plaited - [![](http://www.garnstudio.com/drops/mag/165/47/47-2.jpg)](http://www.garnstudio.com/pattern.php?id=7146&cid=9) - cable knitting - [![](http://www.garnstudio.com/drops/mag/165/27/27-2.jpg)](http://www.garnstudio.com/pattern.php?id=7070&cid=9) - circle knitting - primitive lines - cm specification - [![](http://www.garnstudio.com/drops/mag/167/11/11b-2.jpg)](http://www.garnstudio.com/pattern.php?id=7362&cid=9) - complex and huge pattern - [![](http://www.garnstudio.com/drops/mag/169/18/18-diag2.jpg)](http://www.garnstudio.com/pattern.php?id=7467&cid=9) - easy to sewing pattern ================================================ FILE: knittingpattern/examples/all-instructions.json ================================================ { "version" : "0.1", "type" : "knitting pattern", "comment" : { "markdown" : "This pattern is taken from [garnstudio](http://www.garnstudio.com/pattern.php?id=7350&cid=19).", "picture" : { "type" : "url", "url" : "http://www.garnstudio.com/drops/mag/167/17/17-lp.jpg" } }, "patterns" : [ { "name" : "all-instructions", "id" : "A.2", "rows" : [ { "id" : 1, "color" : "#0000ff", "instructions" : [ {"type" : "bo"}, {"type" : "cdd"}, {"type" : "co"}, {"type" : "default-unknown"}, {"type" : "k2tog"}, {"type" : "knit"}, {"type" : "purl"}, {"type" : "skp"}, {"type" : "yo"} ] } ], "connections" : [ ] } ] } ================================================ FILE: knittingpattern/examples/block4x4.json ================================================ { "type" : "knitting pattern", "version" : "0.1", "comment" : { "content" : "4x4 meshes block", "type" : "markdown" }, "patterns" : [ { "id" : "knit", "name" : "knit 4x4", "rows" : [ { "id" : 1, "instructions" : [ {"id": "1.0", "color": "green"}, {"id": "1.1"}, {"id": "1.2"}, {"id": "1.3"} ] }, { "id" : 3, "instructions" : [ {"id": "3.0"}, {"id": "3.1"}, {"id": "3.2", "color": "green"}, {"id": "3.3"} ] }, { "id" : 2, "instructions" : [ {"id": "2.0"}, {"id": "2.1", "color": "green"}, {"id": "2.2"}, {"id": "2.3"} ] }, { "id" : 4, "instructions" : [ {"id": "4.0"}, {"id": "4.1"}, {"id": "4.2"}, {"id": "4.3", "color": "green"} ] } ], "connections" : [ { "from" : { "id" : 1 }, "to" : { "id" : 2 } }, { "from" : { "id" : 2 }, "to" : { "id" : 3 } }, { "from" : { "id" : 3 }, "to" : { "id" : 4 } } ] } ] } ================================================ FILE: knittingpattern/examples/empty.json ================================================ { "version" : "0.1", "type" : "knitting pattern", "patterns" : [ ] } ================================================ FILE: knittingpattern/examples/negative-rendering.json ================================================ { "type" : "knitting pattern", "version" : "0.1", "comment" : { "content" : "rendering with negative layout indices", "type" : "markdown" }, "patterns" : [ { "id" : "knit", "name" : "knit 4x4", "rows" : [ { "id" : 0, "instructions" : [ {"type":"yo"},{},{},{},{"type":"yo"} ] }, { "id" : 1, "instructions" : [ {"type":"yo"},{},{},{},{},{},{"type":"yo"} ] }, { "id" : 2, "instructions" : [ {"type":"yo"},{},{},{},{},{},{},{},{"type":"yo"} ] }, { "id" : 3, "instructions" : [ {"type":"yo"},{},{},{},{},{},{},{},{},{},{"type":"yo"} ] }, { "id" : -1, "instructions" : [ {"type":"yo"},{},{},{},{},{},{"type":"yo"} ] }, { "id" : -2, "instructions" : [ {"type":"yo"},{},{},{},{},{},{},{},{"type":"yo"} ] }, { "id" : -3, "instructions" : [ {"type":"yo"},{},{},{},{},{},{},{},{},{},{"type":"yo"} ] } ], "connections" : [ { "from" : { "id" : 0 }, "to" : { "id" : 1, "start" : 1 } }, { "from" : { "id" : 1 }, "to" : { "id" : 2, "start" : 1 } }, { "from" : { "id" : 2 }, "to" : { "id" : 3, "start" : 1 } }, { "from" : { "id" : -1 }, "to" : { "id" : 0 } }, { "from" : { "id" : -2 }, "to" : { "id" : -1 } }, { "from" : { "id" : -3 }, "to" : { "id" : -2 } } ] } ] } ================================================ FILE: knittingpattern/examples/new-knitting-pattern.py ================================================ import json s = dict() right_mesh = dict() # type of stitch # k means a knit stitch and p means a purl stitch. Thus, "k2, p2", # means "knit two stitches, purl two stitches". Similarly, # sl st describes a slip stitch, whereas yarn-overs are denoted with yo. right_mesh["type"] = "knit" # purl # scope of stitch # The modifier tog indicates that the stitches should be knitted together, # e.g., "k2tog" indicates that two stitches should be knitted together # as though they were one stitch. psso means "pass the slipped stitch # over". pnso means "pass the next stitch over". right_mesh["scope"] = None # orientation of stitch # The modifier tbl indicates that stitches should be knitted # through the back loop. For example, "p2tog tbl" indicates # that two stitches should be purled together through the back toop. # kwise and pwise connote "knitwise" and "purlwise", usually referring # to a slip stitch. right_mesh["orientation"] = 0 # degrees # insertion point of stitch # k-b and k1b mean "knit into the row below". Similarly, p-b and # p1b mean "purl into the row below". # p tbl; P1 tbl; or P1b: Purl through the back loop. right_mesh["insertion_point"] = None MESHES_IN_ROW_1 = 5 MESHES_IN_ROW_2 = 7 row1 = dict() row1["meshes"] = [right_mesh] * MESHES_IN_ROW_1 # side of work # RS and WS signify the "right side" and "wrong side" of the work. row1["side"] = "right" # wrong row1["id"] = "row1" s["type"] = "knitting pattern" s["version"] = "0.1" s["rows"] = [row1] s["row_connections"] = [{"from": {"id": "row1", "first": 1, "last": MESHES_IN_ROW_1}, "to": {"id": "row2", "first": 1, "last": MESHES_IN_ROW_2}}] print(json.dumps(s, indent=2)) ================================================ FILE: knittingpattern/instructions/bo.json ================================================ [ { "type" : "bo", "number of consumed meshes" : 1, "number of produced meshes" : 0, "title" : { "en-en" : "Simple Bind Off" }, "description" : { "wikipedia" : { "en-en" : "https://en.wikipedia.org/wiki/Binding_off" }, "text" : { "en-en" : "Pass each loop over an adjacent stitch. (The yarn is passed through the final loop to secure the whole chain.) This technique produces a tight edge with little elasticity." } } } ] ================================================ FILE: knittingpattern/instructions/cdd.json ================================================ [ { "type" : "cdd", "number of consumed meshes" : 3, "title" : { "en-en" : "Center Double Decrease" }, "description" : { "text" : { "en-en" : "Knit tree meshes together in the middle." }, "youtube" : { "en-en" : "https://www.youtube.com/watch?v=Wi5JpkOoLCI" } } } ] ================================================ FILE: knittingpattern/instructions/co.json ================================================ [ { "type" : "co", "number of consumed meshes" : 0, "number of produced meshes" : 1, "grid-layout" : { "width" : 1 }, "title" : { "en-en" : "Single Cast On" }, "description" : { "wikipedia" : { "en-en" : "https://en.wikipedia.org/wiki/Casting_on_%28knitting%29" }, "text" : { "en-en" : "An even simpler method, also called the simple cast-on or 'backward loop cast-on,' which involves adding a series of half hitches to the needle. This creates a very stretchy, flexible edge. It is a common approach for adding several stitches to the edge in the middle of a knitted fabric." } } } ] ================================================ FILE: knittingpattern/instructions/k2tog.json ================================================ [ { "type" : "k2tog", "title" : { "en-en" : "Knit 2 Together" }, "number of consumed meshes" : 2, "description" : { "wikipedia" : { "en-en" : "https://en.wikipedia.org/wiki/Knitting_abbreviations#Types_of_knitting_abbreviations" }, "text" : { "en-en" : "Knit two stitches together, as if they were one stitch." } } } ] ================================================ FILE: knittingpattern/instructions/knit.json ================================================ [ { "type" : "knit", "title" : { "en-en" : "Knit", "de-de" : "Rechte Masche" }, "description" : { "text" : { "en-en" : "Knit from the right side, purl from the wrong side." } } } ] ================================================ FILE: knittingpattern/instructions/purl.json ================================================ [ { "type" : "purl", "title" : { "en-en" : "Purl", "de-de" : "Linke Masche" }, "description" : { "text" : { "en-en" : "Purl from the right side, knit from the wrong side." } } } ] ================================================ FILE: knittingpattern/instructions/skp.json ================================================ [ { "type" : "skp", "number of consumed meshes" : 2, "title" : { "en-en" : "Slip Knit Pass" }, "description" : { "wikipedia" : { "en-en" : "https://en.wikipedia.org/wiki/Knitting_abbreviations#Types_of_knitting_abbreviations" }, "text" : { "en-en" : "Slip, knit, pass slipped stitch over the knit stitch (the same as sl1, k1, psso)." } } } ] ================================================ FILE: knittingpattern/instructions/yo.json ================================================ [ { "type" : "yo", "number of consumed meshes" : 0, "title" : { "en-en" : "Yarn Over" }, "description" : { "text" : { "en-en" : "Increase the number of stitches by one." }, "wikipedia" : { "en-en" : "https://en.wikipedia.org/wiki/Yarn_over" } }, "render" : { "z" : 1, "comment" : "Increase the z value to place the yarn over before other meshes." } }, { "type" : "yo twisted", "title" : { "en-en" : "Twisted Yarn Over" }, "number of consumed meshes" : 0, "description" : { "text" : { "en-en" : "Increase the number of stitches by one. Twist the yarn to avoid a hole." }, "wikipedia" : { "en-en" : "https://en.wikipedia.org/wiki/Yarn_over" } }, "render" : { "z" : 1, "comment" : "Increase the z value to place the yarn over before other meshes." } } ] ================================================ FILE: knittingpattern/test/conftest.py ================================================ """This module holds the common test code. .. seealso:: `pytest good practices `__ for why this module exists. """ import os import sys # sys.path makes knittingpattern importable HERE = os.path.dirname(__file__) sys.path.insert(0, os.path.join(HERE, "../..")) __builtins__["HERE"] = HERE ================================================ FILE: knittingpattern/test/pattern/inheritance.json ================================================ { "type" : "knitting pattern", "version" : "0.1", "patterns" : [ { "id" : "color test", "name" : "colored line", "rows" : [ { "id" : "colored", "color": "blue", "instructions" : [ {"color": "green"}, {} ] }, { "id" : "inherited uncolored +instructions", "same as" : "inherited uncolored", "instructions" : [ {}, {"color": "brown"} ] }, { "id" : "inherited colored +instructions", "same as" : "inherited colored", "instructions" : [ {}, {"color": "red"} ] }, { "id" : "uncolored", "instructions" : [ {}, {"color": "yellow"} ] }, { "id" : "inherited uncolored", "same as" : "uncolored" }, { "id" : "inherited colored", "same as" : "colored" } ], "connections" : [ ] } ] } ================================================ FILE: knittingpattern/test/pattern/row_mapping_pattern.json ================================================ { "type" : "knitting pattern", "version" : "0.1", "patterns" : [ { "id" : "A.1", "name" : "A.1", "rows" : [ { "id" : "1.1", "instructions" : [ {"id": "1.1.0"}, {"id": "1.1.1"}, {"id": "1.1.2", "type": "yo"}, {"id": "1.1.3"}, {"id": "1.1.4"} ] }, { "id" : "2.1", "instructions" : [ {"id": "2.1.0"}, {"id": "2.1.1"} ] }, { "id" : "2.2", "instructions" : [ {"id": "2.2.0"}, {"id": "2.2.1"} ] }, { "id" : "3.2", "instructions" : [ {"id": "3.2.0"}, {"id": "3.2.1"} ] }, { "id" : "4.1", "instructions" : [ {"id": "4.1.0"}, {"id": "4.1.1"}, {"id": "4.1.2", "type": "skp"}, {"id": "4.1.4"} ] } ], "connections" : [ { "from" : { "id" : "1.1", "start" : 0 }, "to" : { "id" : "2.1", "start" : 0 }, "meshes" : 2 }, { "from" : { "id" : "1.1", "start" : 3 }, "to" : { "id" : "2.2", "start" : 0 }, "meshes" : 2 }, { "from" : { "id" : "2.2", "start" : 0 }, "to" : { "id" : "3.2", "start" : 0 } }, { "from" : { "id" : "2.1", "start" : 0 }, "to" : { "id" : "4.1", "start" : 0 } }, { "from" : { "id" : "3.2", "start" : 0 }, "to" : { "id" : "4.1", "start" : 3 } } ] } ] } ================================================ FILE: knittingpattern/test/pattern/row_removal_pattern.json ================================================ { "type" : "knitting pattern", "version" : "0.1", "patterns" : [ { "id" : "line", "name" : "line of three meshes", "rows" : [ { "id" : 1, "instructions" : [ {"id": 1.1}, {"id": 1.2} ] }, { "id" : 2, "instructions" : [ {"id": 2.1} ] }, { "id" : 3, "instructions" : [ {"id": 3.1} ] } ], "connections" : [ { "from" : { "id" : 1 }, "to" : { "id" : 2 } }, { "from" : { "id" : 2 }, "to" : { "id" : 3 } } ] } ] } ================================================ FILE: knittingpattern/test/pattern/single_instruction.json ================================================ { "type" : "knitting pattern", "version" : "0.1", "patterns" : [ { "id" : "A.1", "name" : "A.1", "rows" : [ { "id" : 1, "instructions" : [ {} ] }, { "id" : 2, "instructions" : [ {} ] } ], "connections" : [ ] } ] } ================================================ FILE: knittingpattern/test/test_add_and_remove_instructions.py ================================================ """Test the maniipulation of the rows by adding instructions.""" from pytest import fixture, raises from knittingpattern import load_from_relative_file from knittingpattern.Instruction import InstructionNotFoundInRow @fixture def single_instruction_pattern_set(): """Load the pattern set with only one instruction.""" return load_from_relative_file(HERE, "pattern/single_instruction.json") @fixture def pattern(single_instruction_pattern_set): """The pattern which has only one instruction.""" return single_instruction_pattern_set.patterns["A.1"] @fixture def row(pattern): """The row with one instruction.""" return pattern.rows[1] @fixture def row2(pattern): """The row with one instruction.""" return pattern.rows[2] @fixture def instruction(row): """The instruction.""" return row.instructions[0] @fixture def instruction2(row2): """The instruction.""" return row2.instructions[0] @fixture def empty_row(row, instruction): """Now, there is no instruction any more.""" assert instruction row.instructions.pop() return row def test_there_is_only_one_instruction(row): """There should be only one instruction, as claimed many times. If people write that there is only one instruction, we should make that sure!""" assert len(row.instructions) == 1 def test_removing_the_instruction_gives_an_error_when_accessing_its_index( empty_row, instruction): """Obviously a removed instruction is not in its row any more and thus has no index.""" with raises(InstructionNotFoundInRow): instruction.index_in_row assert not instruction.is_in_row() def test_inserting_a_new_instruction_loads_its_config(row): row.instructions.append({}) instruction = row.instructions[-1] assert instruction.type == "knit" assert instruction.is_in_row() assert instruction.row == row assert instruction.index_in_row == 1 def test_insert_an_existing_instruction(row, instruction2, row2): row.instructions.insert(0, instruction2) assert instruction2.row == row assert instruction2.index_in_row == 0 assert row2.instructions == [] def test_transfer_removed_instruction(row, row2): row2.instructions.append(row.instructions.pop()) instruction = row2.instructions[-1] assert instruction.row == row2 ================================================ FILE: knittingpattern/test/test_change_row_mapping.py ================================================ """Test if the mapping of the rows is changed and how.""" from pytest import fixture, raises from knittingpattern import load_from_relative_file as load_relative_file @fixture def patterns(): return load_relative_file(__file__, "pattern/row_removal_pattern.json") @fixture def line(patterns): return patterns.patterns["line"] @fixture def row1(line): return line.rows[1] @fixture def row2(line): return line.rows[2] @fixture def row3(line): return line.rows[3] # produced meshes @fixture def mesh11p(row1): return row1.produced_meshes[0] @fixture def mesh12p(row1): return row1.produced_meshes[1] @fixture def mesh21p(row2): return row2.produced_meshes[0] @fixture def mesh31p(row3): return row3.produced_meshes[0] # consumed meshes @fixture def mesh11c(row1): return row1.consumed_meshes[0] @fixture def mesh12c(row1): return row1.consumed_meshes[1] @fixture def mesh21c(row2): return row2.consumed_meshes[0] @fixture def mesh31c(row3): return row3.consumed_meshes[0] @fixture def connected_meshes(mesh11p, mesh21c, mesh21p, mesh31c): return (mesh11p, mesh21c, mesh21p, mesh31c) @fixture def disconnected_meshes(mesh11c, mesh12c, mesh12p, mesh31p): return (mesh11c, mesh12c, mesh12p, mesh31p) @fixture def meshes(connected_meshes, disconnected_meshes): return connected_meshes + disconnected_meshes @fixture def connections(mesh11p, mesh21c, mesh21p, mesh31c): return ((mesh11p, mesh21c), (mesh21p, mesh31c)) @fixture def two_way_connections(mesh11p, mesh21c, mesh21p, mesh31c): return ((mesh11p, mesh21c), (mesh21c, mesh11p), (mesh21p, mesh31c), (mesh31c, mesh21p)) @fixture def produced_meshes(mesh11p, mesh12p, mesh21p, mesh31p): return (mesh11p, mesh12p, mesh21p, mesh31p) @fixture def consumed_meshes(mesh11c, mesh12c, mesh21c, mesh31c): return (mesh11c, mesh12c, mesh21c, mesh31c) def pytest_generate_tests(metafunc): if "connect" in metafunc.fixturenames: metafunc.parametrize("connect", [0, 1]) if "disconnect" in metafunc.fixturenames: metafunc.parametrize("disconnect", [0, 1]) def connect_meshes(mesh1, mesh2, connect): if connect == 1: mesh2, mesh1 = mesh1, mesh2 mesh1.connect_to(mesh2) def disconnect_meshes(mesh1, mesh2, disconnect): mesh = (mesh1, mesh2)[disconnect] mesh.disconnect() class TestLine(object): """Make sure the pattern is what we expect.""" def test_consumed_meshes_of_row1(self, row1, mesh11c, mesh12c): for mesh in (mesh11c, mesh12c): assert not mesh.is_produced() assert mesh.is_consumed() assert mesh.consuming_row == row1 def test_produced_meshes_of_row1(self, row1, mesh11p, mesh12p): for mesh in (mesh11p, mesh12p): assert mesh.is_produced() assert mesh.producing_row == row1 assert mesh11p.is_consumed() assert not mesh12p.is_consumed() def test_consumed_mesh_of_row2(self, row2, mesh21c): assert mesh21c.is_consumed() assert mesh21c.is_produced() assert mesh21c.consuming_row == row2 def test_produced_mesh_of_row2(self, row2, mesh21p): assert mesh21p.is_consumed() assert mesh21p.is_produced() assert mesh21p.producing_row == row2 def test_consumed_mesh_of_row3(self, row3, mesh31c): assert mesh31c.is_consumed() assert mesh31c.is_produced() assert mesh31c.consuming_row == row3 def test_produced_mesh_of_row3(self, row3, mesh31p): assert not mesh31p.is_consumed() assert mesh31p.is_produced() assert mesh31p.producing_row == row3 def test_is_connected(self, connected_meshes): for mesh in connected_meshes: assert mesh.is_connected() def test_is_disconnected(self, disconnected_meshes): for mesh in disconnected_meshes: assert not mesh.is_connected() with raises(AssertionError): mesh.as_consumed_mesh() mesh.as_produced_mesh() def test_equality(self, connections): for produced_mesh, consumed_mesh in connections: assert consumed_mesh.as_produced_mesh() == produced_mesh assert produced_mesh.as_consumed_mesh() == consumed_mesh def test_is_connected_to(self, two_way_connections): for m1, m2 in two_way_connections: assert m1.is_connected_to(m2) def test_disconnected_from(self, connections, meshes): """Test all the meshes that are disconnected from each other.""" for m1 in meshes: assert m1 == m1 for m2 in meshes: if m1 is m2: continue assert m1 != m2 assert not m1 == m2 if (m1, m2) not in connections and (m2, m1) not in connections: assert not m1.is_connected_to(m2) if m1.is_connected() and m1 != m2: assert m1.as_produced_mesh() != m2 or m1 == m2 assert m1.as_consumed_mesh() != m2 or m1 == m2 def test_as_produced_mesh(self, produced_meshes): for produced_mesh in produced_meshes: assert produced_mesh.as_produced_mesh() == produced_mesh def test_as_consumed_mesh(self, consumed_meshes): for consumed_mesh in consumed_meshes: assert consumed_mesh.as_consumed_mesh() == consumed_mesh def test_remove_a_connection(row1, row2, mesh11p, mesh21c, disconnect): disconnect_meshes(mesh11p, mesh21c, disconnect) assert mesh11p.is_produced() assert not mesh11p.is_consumed() assert mesh11p.producing_row == row1 assert not mesh21c.is_produced() assert mesh21c.is_consumed() assert mesh21c.consuming_row == row2 assert mesh11p != mesh21c with raises(Exception): mesh11p.as_consumed_mesh() with raises(Exception): mesh21c.as_produced_mesh() def test_replace_a_connection(disconnect, connect, mesh21p, mesh31c, mesh12p, row1, row3): """Remove a connection and create one with a common mesh. Remove a connection between mesh21p and mesh31c and create a connection between mesh12p and mesh31c. """ disconnect_meshes(mesh21p, mesh31c, disconnect) connect_meshes(mesh31c, mesh12p, connect) assert not mesh21p.is_connected() assert mesh31c.is_connected() assert mesh12p.is_connected() assert mesh31c.producing_row == row1 assert mesh12p.consuming_row == row3 assert mesh31c.as_produced_mesh() == mesh12p assert mesh12p.as_consumed_mesh() == mesh31c def test_connect_to_a_connected_location(mesh12p, mesh31c, mesh21p, connect): connect_meshes(mesh12p, mesh31c, connect) assert mesh12p.is_connected_to(mesh31c) assert not mesh12p.is_connected_to(mesh21p) assert not mesh31c.is_connected_to(mesh21p) assert not mesh21p.is_connected() def test_connect_to_a_connected_location_with_connected_mesh( mesh11p, mesh31c, mesh21c, mesh21p, connect): connect_meshes(mesh11p, mesh31c, connect) assert mesh11p.is_connected_to(mesh31c) assert not mesh21c.is_connected() assert not mesh21p.is_connected() def test_can_connect(connected_meshes, consumed_meshes, produced_meshes): for consumed_mesh in consumed_meshes: for produced_mesh in produced_meshes: can_connect = consumed_mesh not in connected_meshes and \ produced_mesh not in connected_meshes assert produced_mesh.can_connect_to(consumed_mesh) == can_connect def test_create_new_connection(mesh31p, mesh12c, connect, row1, row3): connect_meshes(mesh31p, mesh12c, connect) assert mesh31p.is_connected() assert mesh12c.is_connected() assert mesh12c.producing_row == row3 assert mesh31p.consuming_row == row1 assert mesh12c.as_produced_mesh() == mesh31p assert mesh31p.as_consumed_mesh() == mesh12c def test_disconnect_disconnected(mesh12c): mesh12c.disconnect() assert not mesh12c.is_connected() ================================================ FILE: knittingpattern/test/test_default_instructions.py ================================================ from pytest import fixture import pytest from knittingpattern.InstructionLibrary import DefaultInstructions, \ default_instructions DEFAULT_INSTRUCTIONS = { "knit": (1, 1), "purl": (1, 1), "skp": (2, 1), "yo": (0, 1), "yo twisted": (0, 1), "k2tog": (2, 1), "bo": (1, 0), "cdd": (3, 1), "co": (0, 1) } @fixture def default(): return DefaultInstructions() @pytest.mark.parametrize("type_,value", DEFAULT_INSTRUCTIONS.items()) def test_mesh_consumption(default, type_, value): assert default[type_].number_of_consumed_meshes == value[0] @pytest.mark.parametrize("type_,value", DEFAULT_INSTRUCTIONS.items()) def test_mesh_production(default, type_, value): assert default[type_].number_of_produced_meshes == value[1] @pytest.mark.parametrize("type_", DEFAULT_INSTRUCTIONS.keys()) def test_description_present(default, type_): assert default[type_].description UNTEDTED_MESSAGE = "No default instructions shall be untested." def test_all_default_instructions_are_tested(default): untested_instructions = \ set(default.loaded_types) - set(DEFAULT_INSTRUCTIONS) assert not untested_instructions, UNTEDTED_MESSAGE def test_default_instructions_is_a_singleton(): assert default_instructions() is default_instructions() def test_default_instructions_are_an_instance_of_the_class(): assert isinstance(default_instructions(), DefaultInstructions) ================================================ FILE: knittingpattern/test/test_dump_json.py ================================================ from pytest import fixture from unittest.mock import MagicMock from knittingpattern.Dumper import JSONDumper import json from knittingpattern.ParsingSpecification import ParsingSpecification @fixture def obj(): return ["123", 123] @fixture def dumper(obj): def dump(): return obj return JSONDumper(dump) @fixture def parser(): return MagicMock() def test_dump_object(dumper, obj): assert dumper.object() == obj def test_dump_string(dumper, obj): assert dumper.string() == json.dumps(obj) def test_dump_to_temporary_file(dumper, obj): temp_path = dumper.temporary_path() with open(temp_path) as file: obj2 = json.load(file) assert obj2 == obj def test_dump_to_knitting_pattern(dumper, parser, obj): spec = ParsingSpecification(new_parser=parser) dumper.knitting_pattern(spec) parser.assert_called_with(spec) parser(spec).knitting_pattern_set.assert_called_with(obj) def test_string_representation(dumper): string = repr(dumper) assert "JSONDumper" in string ================================================ FILE: knittingpattern/test/test_dumper.py ================================================ from pytest import fixture from knittingpattern.Dumper import ContentDumper from io import StringIO, BytesIO import os STRING = "asdf1234567890\u1234" BYTES = STRING.encode("UTF-8") @fixture def unicode(): def dump_to_string(file): file.write(STRING[:1]) file.write(STRING[1:]) return ContentDumper(dump_to_string) @fixture def binary(): def dump_to_bytes(file): file.write(BYTES[:1]) file.write(BYTES[1:]) return ContentDumper(dump_to_bytes, text_is_expected=False) @fixture def no_encode_text(): return ContentDumper(lambda file: file.write("asd"), encoding=None) @fixture def no_encode_binary(): return ContentDumper(lambda file: file.write(b"asd"), text_is_expected=False, encoding=None) def pytest_generate_tests(metafunc): if 'save_to' in metafunc.fixturenames: metafunc.parametrize("save_to", [binary(), unicode()]) @fixture def temp_file(save_to): return save_to.temporary_file() @fixture def binary_temp_file(save_to): return save_to.temporary_binary_file() @fixture def stringio(): return StringIO() def assert_string_is_file_content(file): file.seek(0) assert file.read() == STRING def assert_string_is_path_content(path): with open(path, encoding="UTF-8") as file: assert file.read() == STRING def assert_string_is_binary_content(file): file.seek(0) assert file.read() == BYTES def test_string_is_long(): assert len(STRING) > 5 def test_dump_to_string(save_to): assert save_to.string() == STRING def test_dump_to_file(save_to, stringio): save_to.file(stringio) assert_string_is_file_content(stringio) def test_dump_is_behind_content_in_file(save_to, stringio): save_to.file(stringio) assert stringio.read() == "" def test_dump_to_path(save_to, tmpdir): path = tmpdir.mkdir("sub").join("temp.txt").strpath save_to.path(path) assert_string_is_path_content(path) def test_dump_to_temp_path(save_to): path = save_to.temporary_path() assert_string_is_path_content(path) def test_dump_to_temporary_file(temp_file): assert_string_is_file_content(temp_file) def test_dump_to_temporary_binary_file(binary_temp_file): assert_string_is_binary_content(binary_temp_file) def test_temporary_file_is_deleted_on_default(temp_file): assert_temporary_file_is_deleted(temp_file) def test_binary_temporary_file_is_deleted_on_default(binary_temp_file): assert_temporary_file_is_deleted(binary_temp_file) def assert_temporary_file_is_deleted(temp_file): temp_file.close() assert not os.path.isfile(temp_file.name) def assert_temporary_file_is_not_deleted(temp_file): temp_file.close() assert os.path.isfile(temp_file.name) def test_temporary_file_exists(temp_file): assert os.path.isfile(temp_file.name) def test_binary_temporary_file_exists(binary_temp_file): assert os.path.isfile(binary_temp_file.name) def test_temporary_file_has_option_for_deletion(save_to): file = save_to.temporary_file(delete_when_closed=False) assert_temporary_file_is_not_deleted(file) def test_binary_temporary_file_has_option_for_deletion(save_to): file = save_to.binary_temporary_file(delete_when_closed=False) assert_temporary_file_is_not_deleted(file) def test_file_returns_new_file(save_to): file = save_to.file() assert_string_is_file_content(file) def test_dump_is_behind_content_in_new_file(save_to, stringio): file = save_to.file() assert file.read() == "" def test_bytes(save_to): assert save_to.bytes() == BYTES def test_encoding(save_to): assert save_to.encoding == "UTF-8" def test_new_binary_file(save_to): file = save_to.binary_file() file.seek(0) assert file.read() == BYTES def test_binary_file(save_to): file = BytesIO() save_to.binary_file(file) file.seek(0) assert file.read() == BYTES def test_test_binary_file_is_at_end(save_to): assert not save_to.binary_file().read() def test_encoding_is_none(no_encode_binary, no_encode_text): assert no_encode_text.encoding is None assert no_encode_binary.encoding is None def test_temporary_path_has_extension(save_to): assert save_to.temporary_path(extension=".png").endswith(".png") assert save_to.temporary_path(extension=".JPG").endswith(".JPG") def test_string_representation(save_to): string = repr(save_to) assert string.startswith("") assert save_to.encoding in string ================================================ FILE: knittingpattern/test/test_example_code.py ================================================ """The files contain example code that should work.""" def test_load_from_example_and_create_svg(): """Test :meth:`knittingpattern.load_from`.""" import knittingpattern k = knittingpattern.load_from().example("Cafe.json") k.to_svg(25).temporary_path(".svg") ================================================ FILE: knittingpattern/test/test_example_rows.py ================================================ """Test properties of rows.""" from pytest import fixture, raises from test_examples import charlotte as _charlotte @fixture def charlotte(): return _charlotte() @fixture def a1(charlotte): """:return: the pattern ``"A.1"`` in charlotte""" return charlotte.patterns["A.1"] @fixture def a2(charlotte): """:return: the pattern ``"A.2"`` in charlotte""" return charlotte.patterns["A.2"] def test_number_of_rows(a1): """``"A.1"`` should have three rows that can be accessed""" assert len(a1.rows) == 3 with raises(IndexError): a1.rows.at(3) def test_row_ids(a1): """Rows in ``"A.1"`` have ids.""" assert a1.rows.at(0).id == ("A.1", "empty", "1") assert a1.rows.at(2).id == ("A.1", "lace", "1") def test_access_by_row_ids(a1): """Rows in ``"A.1"`` can be accessed by their ids.""" assert a1.rows[("A.1", "empty", "1")] == a1.rows.at(0) def test_iterate_on_rows(a1): """For convinience one can iterate over the rows.""" assert list(iter(a1.rows)) == [a1.rows.at(0), a1.rows.at(1), a1.rows.at(2)] ================================================ FILE: knittingpattern/test/test_examples.py ================================================ from pytest import fixture, raises import os import knittingpattern EXAMPLES_PATH = os.path.join(os.path.dirname(__file__), "../examples") CAFE_PATH = os.path.join(EXAMPLES_PATH, "Cafe.json") CHARLOTTE_PATH = os.path.join(EXAMPLES_PATH, "Charlotte.json") CAFE_STRING = open(CAFE_PATH).read() CHARLOTTE_STRING = open(CHARLOTTE_PATH).read() @fixture def charlotte(): return knittingpattern.load_from_string(CHARLOTTE_STRING) @fixture def cafe(): return knittingpattern.load_from_string(CAFE_STRING) def test_number_of_patterns(charlotte): assert len(charlotte.patterns) == 2 with raises(IndexError): charlotte.patterns.at(3) @fixture def pattern_0(charlotte): return charlotte.patterns.at(0) @fixture def pattern_1(charlotte): return charlotte.patterns.at(1) def test_names(pattern_0, pattern_1): assert pattern_0.name == "A.1" assert pattern_1.name == "A.2" def test_ids(pattern_0, pattern_1): assert pattern_0.id == "A.1" assert pattern_1.id == "A.2" def test_access_with_id(charlotte): assert charlotte.patterns["A.1"] == charlotte.patterns.at(0) def test_iterate_on_pattern(charlotte): patterns = charlotte.patterns assert list(iter(patterns)) == [patterns.at(0), patterns.at(1)] ================================================ FILE: knittingpattern/test/test_id_collection.py ================================================ from pytest import fixture, raises from knittingpattern.IdCollection import IdCollection from collections import namedtuple I = namedtuple("Item", ["id"]) @fixture def c(): return IdCollection() def test_no_object(c): assert not c assert not list(c) def test_add_object(c): c.append(I("123")) c.append(I("122")) assert c.at(0).id == "123" assert c.at(1).id == "122" assert c["123"].id == "123" assert c["122"].id == "122" def test_length(c): assert len(c) == 0 c.append(I(1)) assert len(c) == 1 c.append(I("")) assert len(c) == 2 def test_at_raises_keyerror(c): with raises(KeyError): c["unknown-id"] ================================================ FILE: knittingpattern/test/test_instruction.py ================================================ from pytest import fixture from knittingpattern.Instruction import Instruction import pytest @fixture def default_instruction(): return Instruction({}) @fixture def purl(): return Instruction({"type": "purl"}) @fixture def yo(): return Instruction({"type": "yo", "number of consumed meshes": 0}) @fixture def bindoff(): return Instruction({"type": "bindoff", "number of produced meshes": 0}) @fixture def colored_instruction(): return Instruction({"type": "purl", "color": "blue", "custom name": "custom value", "not inherited value": 1}, [{"color": "green", "inherited value": 0, "not inherited value": 2}, {"other inherited value": 4}, {"other inherited value": 0}]) def test_default_type(default_instruction): assert default_instruction.type == "knit" assert default_instruction.does_knit() assert not default_instruction.does_purl() def test_default_color(default_instruction): assert not default_instruction.has_color() assert default_instruction.color is None def test_width(default_instruction, purl): assert default_instruction.number_of_consumed_meshes == 1 assert default_instruction.number_of_produced_meshes == 1 assert purl.number_of_consumed_meshes == 1 assert purl.number_of_produced_meshes == 1 def test_purl_is_not_knit(purl): assert not purl.does_knit() assert purl.does_purl() def test_color(colored_instruction): assert colored_instruction.color == "blue" assert "custom name" in colored_instruction assert colored_instruction["custom name"] == "custom value" def test_inheritance(colored_instruction): assert colored_instruction["not inherited value"] == 1 assert colored_instruction["inherited value"] == 0 assert colored_instruction["other inherited value"] == 4 def test_purl_produces_meshes(purl): assert purl.produces_meshes() def test_purl_consumes_meshes(purl): assert purl.consumes_meshes() def test_yarn_over_consumes_no_meshes(yo): assert yo.number_of_consumed_meshes == 0 assert not yo.consumes_meshes() def test_yarn_over_produces_meshes(yo): assert yo.number_of_produced_meshes == 1 assert yo.produces_meshes() def test_bindoff_consumes_meshes(bindoff): assert bindoff.number_of_consumed_meshes == 1 assert bindoff.consumes_meshes() def test_bindoff_produces_no_meshes(bindoff): assert bindoff.number_of_produced_meshes == 0 assert not bindoff.produces_meshes() class TestInstructionColors(object): """Test the Instruction.colors attribute.""" @pytest.mark.parametrize("spec,colors", [ ({}, [None]), ({"color": "blue"}, ["blue"]), ({"color": 123}, [123])]) def test_get_colors_from_color_specification(self, spec, colors): instruction = Instruction(spec) assert instruction.colors == colors ================================================ FILE: knittingpattern/test/test_instruction_library.py ================================================ from pytest import fixture from knittingpattern.InstructionLibrary import InstructionLibrary DESCRIPTION = "here you can see how to knit: URL" DESCRIPTION_2 = "well, this is kinda a different description" library_instructions = [ { "type": "knit", "description": DESCRIPTION }, { "type": "purl", "inverse": "knit" }, { "type": "extravagant knit", "color": "green", "specialattribute": True } ] # TODO: What happens if an instruction type is defined multiple times? Error? @fixture def library(): return InstructionLibrary().load.object(library_instructions) @fixture def library2(library): spec = [ {"type": "added", "a": 1}, {"type": "knit", "description": DESCRIPTION_2} ] library.load.object(spec) return library @fixture def knit(library): return library.as_instruction({"type": "knit"}) @fixture def purl(library): return library.as_instruction({"type": "purl", "color": "white"}) @fixture def custom_knit(library): return library.as_instruction({"type": "extravagant knit"}) def test_knit_type_attributes(knit): assert knit.type == "knit" assert knit["description"] == DESCRIPTION assert knit["type"] == knit.type def test_knit_has_no_color(knit): assert "color" not in knit assert "type" in knit def test_purl_has_color(purl): assert purl.color == "white" assert "color" in purl def test_not_everyting_is_known_by_purl(purl): assert "asd" not in purl assert "inverse" in purl assert purl["inverse"] == "knit" def test_custom_type(custom_knit): assert custom_knit["specialattribute"] def test_default_instruction_is_knit(library): assert library.as_instruction({})["type"] == "knit" def test_library_does_not_forget_old_values(library2): assert library2.as_instruction({"knit"}) def test_library_can_load_multiple_times(library2): assert library2.as_instruction({"type": "added"})["a"] == 1 def test_library_handles_loading_several_instructions_with_same_type(library2): assert library2.as_instruction({})["description"] == DESCRIPTION_2 def test_access_via_type(library): assert library["knit"]["type"] == "knit" UNLOADED = "unloaded type" def test_when_library_load_instruction_it_is_in_its_types(library2): library2.add_instruction({"type": UNLOADED}) assert UNLOADED in library2.loaded_types def test_unloaded_instruction_is_not_in_the_types(library2): assert UNLOADED not in library2.loaded_types ================================================ FILE: knittingpattern/test/test_instruction_row_inheritance.py ================================================ """Test that the color attribute is inherited properly.""" from pytest import fixture import pytest from knittingpattern import load_from_relative_file @fixture(scope="module") def coloring_pattern(): """The pattern with one colored line and a uncolored line.""" patterns = load_from_relative_file(__name__, "pattern/inheritance.json") return patterns.patterns["color test"] INSTRUCTION_INHERITANCE = [ ("uncolored", 0, None), # Neither row nor instruction have a color. ("uncolored", 1, "yellow"), # Instruction uses own color. ("colored", 0, "green"), # Row color is used, instruction has none. ("colored", 1, "blue"), # Instruction prefers own color before row's. ("inherited uncolored", 0, None), ("inherited uncolored", 1, "yellow"), ("inherited colored", 0, "green"), ("inherited colored", 1, "blue"), ("inherited uncolored +instructions", 0, None), ("inherited uncolored +instructions", 1, "brown"), ("inherited colored +instructions", 0, "blue"), ("inherited colored +instructions", 1, "red")] @pytest.mark.parametrize("row_id,instuction_index,color", INSTRUCTION_INHERITANCE) def test_instruction_has_color(coloring_pattern, row_id, instuction_index, color): """Test that the instructions correctly inherit from their row.""" row = coloring_pattern.rows[row_id] instruction = row.instructions[instuction_index] assert instruction.color == color ROW_INHERITANCE = [ ("colored", "blue"), ("uncolored", None), ("inherited colored", "blue"), ("inherited uncolored", None), ("inherited colored +instructions", "blue"), ("inherited uncolored +instructions", None)] @pytest.mark.parametrize("row_id,color", ROW_INHERITANCE) def test_rows_have_color(coloring_pattern, row_id, color): """Test that the rows correctly inherit or define their color.""" row = coloring_pattern.rows[row_id] assert row.color == color ================================================ FILE: knittingpattern/test/test_instructions/recursion/test_instruction_3.json ================================================ [ { "type" : "test3", "value" : 3 } ] ================================================ FILE: knittingpattern/test/test_instructions/recursion/test_instruction_4.json ================================================ [ { "type" : "test4", "value" : 4 } ] ================================================ FILE: knittingpattern/test/test_instructions/test_instruction_1.json ================================================ [ { "type" : "test1", "value" : 1 } ] ================================================ FILE: knittingpattern/test/test_instructions/test_instruction_2.json ================================================ [ { "type" : "test2", "value" : 2 } ] ================================================ FILE: knittingpattern/test/test_knittingpattern.py ================================================ from knittingpattern import new_knitting_pattern import knittingpattern.KnittingPattern as KnittingPatternModule from knittingpattern.KnittingPattern import KnittingPattern from unittest.mock import Mock from test_row_instructions import a1 import knittingpattern from pytest import fixture class TestInstructionColors(object): """Test KnittingPattern.instruction_colors.""" @fixture def unique(self, monkeypatch): mock = Mock() monkeypatch.setattr(KnittingPatternModule, "unique", mock) return mock @fixture def rows_in_knit_order(self, rows, monkeypatch): mock = Mock() monkeypatch.setattr(KnittingPattern, "rows_in_knit_order", mock) mock.return_value = rows return mock @fixture def rows(self): return [Mock(), Mock(), Mock()] @fixture def knittingpattern(self, rows): return KnittingPattern(Mock(), Mock(), Mock(), Mock()) def test_result(self, knittingpattern, unique, rows_in_knit_order): assert knittingpattern.instruction_colors == unique.return_value def test_call_arguments(self, knittingpattern, unique, rows, rows_in_knit_order): knittingpattern.instruction_colors instruction_colors = [row.instruction_colors for row in rows] unique.assert_called_once_with(instruction_colors) def test_chalotte(self, a1): assert a1.instruction_colors == [None] def test_cafe(self): pattern = knittingpattern.load_from().example("Cafe.json").first colors = ["mocha latte", "dark brown", "brown", "white", ] assert pattern.instruction_colors == colors ================================================ FILE: knittingpattern/test/test_load_instructions.py ================================================ from pytest import fixture import os from knittingpattern.InstructionLibrary import InstructionLibrary @fixture def lib(): return InstructionLibrary() def test_load_from_relative_file(lib): relative_path = "test_instructions/test_instruction_1.json" lib.load.relative_file(__file__, relative_path) assert lib.as_instruction({"type": "test1"})["value"] == 1 assert "value" not in lib.as_instruction({"type": "test2"}) def test_load_from_relative_folder(lib): lib.load.relative_folder(__file__, "test_instructions") assert lib.as_instruction({"type": "test1"})["value"] == 1 assert lib.as_instruction({"type": "test2"})["value"] == 2 def test_load_from_folder(lib): folder = os.path.join(os.path.dirname(__file__), "test_instructions") lib.load.folder(folder) assert lib.as_instruction({"type": "test2"})["value"] == 2 assert lib.as_instruction({"type": "test1"})["value"] == 1 def test_loading_from_folder_recursively(lib): lib.load.relative_folder(__file__, "test_instructions") assert lib.as_instruction({"type": "test3"})["value"] == 3 ================================================ FILE: knittingpattern/test/test_loader.py ================================================ from pytest import fixture import os import pytest from knittingpattern.Loader import ContentLoader, JSONLoader, PathLoader EXAMPLES_DIRECTORY = os.path.join(HERE, "..", "examples") @fixture def result(): return [] @fixture def loader(result): def process(obj): result.append(obj) return len(result) def chooses_path(path): return "_2" in os.path.basename(path) return ContentLoader(process, chooses_path) @fixture def path_loader(): return PathLoader(lambda path: path) @fixture def jsonloader(result): return JSONLoader(result.append) def test_loading_object_does_nothing(loader, result): obj = [] loader.string(obj) assert result[0] is obj def test_processing_result_is_returned(loader): assert loader.string(None) == 1 assert loader.string(None) == 2 def test_json_loader_loads_json(jsonloader, result): jsonloader.string("{\"x\": 1}") assert result == [{"x": 1}] def test_loader_would_like_to_load_path(loader): assert loader.chooses_path("x_2.asd") def test_loader_does_not_like_certain_paths(loader): assert not loader.chooses_path("x_1.asd") def test_loader_can_select_paths_it_likes(loader): assert loader.choose_paths(["_1", "_2", "_3"]) == ["_2"] assert loader.choose_paths(["_123", "3_2", "4_2.as"]) == ["3_2", "4_2.as"] def test_loading_from_directory_selects_paths(loader): paths_to_load = [] loader.path = lambda path: paths_to_load.append(path) assert loader.relative_folder(__name__, "test_instructions") assert len(paths_to_load) == 1 assert paths_to_load[0].endswith("test_instruction_2.json") def example_path(example): return os.path.abspath(os.path.join(EXAMPLES_DIRECTORY, example)) @pytest.mark.parametrize("example", os.listdir(EXAMPLES_DIRECTORY)) def test_load_example(path_loader, example): expected_path = example_path(example) generated_path = os.path.abspath(path_loader.example(example)) assert generated_path == expected_path def test_load_examples(path_loader): example_paths = set() for root, _, examples in os.walk(EXAMPLES_DIRECTORY): for example in examples: example_paths.add(os.path.abspath(os.path.join(root, example))) generated_paths = list(map(os.path.abspath, path_loader.examples())) assert set(generated_paths) == example_paths ================================================ FILE: knittingpattern/test/test_parsing.py ================================================ from pytest import fixture, raises import knittingpattern import json EMPTY_PATTERN = { "version": "0.1", "type": "knitting pattern" } @fixture def temp_empty_pattern_path(tmpdir): p = tmpdir.mkdir("sub").join("empty_pattern.knit") with open(p.strpath, "w") as f: json.dump(EMPTY_PATTERN, f) return p.strpath def assert_is_pattern(pattern): assert pattern.type == "knitting pattern" assert pattern.version == "0.1" def test_can_import_empty_pattern_from_object(): pattern = knittingpattern.load_from_object(EMPTY_PATTERN) assert_is_pattern(pattern) def test_can_import_empty_pattern_from_string(): json_string = json.dumps(EMPTY_PATTERN) pattern = knittingpattern.load_from_string(json_string) assert_is_pattern(pattern) def test_can_import_empty_pattern_from_file_object(temp_empty_pattern_path): with open(temp_empty_pattern_path) as file: pattern = knittingpattern.load_from_file(file) assert_is_pattern(pattern) def test_can_import_empty_pattern_from_path(temp_empty_pattern_path): pattern = knittingpattern.load_from_path(temp_empty_pattern_path) assert_is_pattern(pattern) def test_knitting_pattern_type_is_present(): with raises(ValueError): knittingpattern.load_from_object({}) def test_knitting_pattern_type_is_correct(): with raises(ValueError): knittingpattern.load_from_object({"type": "knitting pattern2"}) def test_load_from_url(temp_empty_pattern_path): url = "file:///" + temp_empty_pattern_path pattern = knittingpattern.load_from_url(url) assert_is_pattern(pattern) ================================================ FILE: knittingpattern/test/test_row_instructions.py ================================================ """These tests access the instructions in rows.""" from pytest import fixture, raises from test_examples import charlotte as _charlotte from knittingpattern.Instruction import InstructionNotFoundInRow import pytest from knittingpattern.Row import Row from unittest.mock import Mock from knittingpattern.Parser import default_parser @fixture def a1(): """:return: the pattern ``"A.1"`` in charlotte""" return _charlotte().patterns["A.1"] @fixture def row0(a1): return a1.rows.at(0) @fixture def row1(a1): return a1.rows.at(1) @fixture def row2(a1): return a1.rows.at(2) @fixture def mesh0(row0): mesh = row0.produced_meshes[0] return mesh @fixture def instruction0(row0): return row0.instructions[0] @fixture def instruction1(row1): return row1.instructions[0] @fixture def removed_instruction(row0): return row0.instructions.pop(1) def test_row0_consumes_empty_meshes(row0): assert len(row0.consumed_meshes) == 5 assert not any(mesh.is_produced() for mesh in row0.consumed_meshes) def test_consumed_meshes_have_index(row0): for i in range(5): mesh = row0.consumed_meshes[i] assert mesh.index_in_consuming_row == i assert mesh.consuming_row == row0 def test_row0_produces_5_meshes(row0): assert len(row0.produced_meshes) == 5 assert all(mesh.is_knit() for mesh in row0.produced_meshes) def test_row0_meshes_point_also_to_row1(mesh0, row0, row1): assert mesh0.producing_row == row0 assert mesh0.consuming_row == row1 def test_row0_instruction_produces_mesh_0(mesh0, instruction0): assert instruction0 == mesh0.producing_instruction assert instruction0.produced_meshes == [mesh0] assert instruction0.number_of_produced_meshes == 1 def test_instruction0_is_knit(instruction0): assert instruction0.does_knit() def test_instruction_position_in_row(row0, instruction0): assert instruction0.row == row0 assert instruction0.index_in_row == 0 assert row0.instructions[0] == instruction0 def test_mesh0_is_consumed_by_instruction1(mesh0, instruction1): assert mesh0.consuming_instruction == instruction1 assert instruction1.consumed_meshes[0].as_produced_mesh() == mesh0 def test_instruction1_is_knit(instruction1): assert instruction1.does_knit() def test_instruction1_position_in_row(instruction1): assert instruction1.index_in_row == 0 def test_mesh0_is_produced(mesh0): assert mesh0.is_produced() assert mesh0.is_consumed() def test_instruction0_builds_on_unproduced_meshes(instruction0): assert not instruction0.consumed_meshes[0].is_produced() @fixture def skp(row2): return row2.instructions[0] @fixture def yo(row2): return row2.instructions[1] def test_yarn_over(yo): assert yo.number_of_consumed_meshes == 0 assert yo.number_of_produced_meshes == 1 def test_skp(skp): assert skp.number_of_consumed_meshes == 2 assert skp.number_of_produced_meshes == 1 def test_position_in_row2(skp, yo, row2): assert skp.row == row2 assert yo.row == row2 assert skp.index_in_row == 0 assert yo.index_in_row == 1 def test_skp_consumed_meshes_from_row1(skp, row1, row2): assert len(skp.produced_meshes) == 1 assert len(skp.consumed_meshes) == 2 m1, m2 = skp.consumed_meshes assert m1.consuming_instruction == skp assert m1.consuming_row == row2 assert m1.producing_row == row1 assert m1.index_in_producing_row == 0 assert m1.is_produced() assert m1.is_consumed() assert m2.consuming_instruction == skp assert m2.consuming_row == row2 assert m2.producing_row == row1 assert m2.index_in_producing_row == 1 assert m2.index_in_consuming_row == 1 def test_skp_produces_one_mesh(skp): assert len(skp.produced_meshes) == 1 def test_skp_produced_meshes(skp, row2): m = skp.produced_meshes[0] assert m.producing_instruction == skp assert m.is_produced() assert not m.is_consumed() assert m.index_in_producing_row == 0 assert m.producing_row == row2 def test_yarn_over_consumes_no_meshes(yo): assert yo.consumed_meshes == [] def test_yarn_over_produces_a_mesh(yo): assert len(yo.produced_meshes) == 1 m = yo.produced_meshes[0] assert m.producing_instruction == yo assert m.producing_row == yo.row assert m.index_in_producing_row == 1 def test_previous_instruction(row0, instruction0): assert row0.instructions[1].previous_instruction_in_row == instruction0 def test_next_instruction(row0, instruction0): assert instruction0.next_instruction_in_row == row0.instructions[1] def test_previous_instruction_is_None_at_border(instruction0): assert instruction0.previous_instruction_in_row is None def test_next_instruction_is_None_at_border(row0): assert row0.instructions[-1].next_instruction_in_row is None def test_index_of_instruction_does_not_change(instruction0): index1 = instruction0.index_in_row index2 = instruction0.index_in_row assert index1 == index2 def test_repr(instruction0): string = repr(instruction0) assert string.startswith("<" + instruction0.__class__.__name__) assert str(instruction0.index_in_row) in string assert repr(instruction0.row) in string def test_instruction_consumes_no_mesh_but_has_mesh_index(yo): assert yo.index_of_first_consumed_mesh_in_row == 2 assert yo.index_of_last_consumed_mesh_in_row == 1 def test_index_of_last_produced_mesh_is_same_as_first(yo): first = yo.index_of_first_produced_mesh_in_row last = yo.index_of_last_produced_mesh_in_row assert first == last def test_removed_instruction_raises_exception(removed_instruction): with raises(InstructionNotFoundInRow): removed_instruction.index_of_first_produced_mesh_in_row with raises(InstructionNotFoundInRow): removed_instruction.index_of_last_produced_mesh_in_row with raises(InstructionNotFoundInRow): removed_instruction.index_of_first_consumed_mesh_in_row with raises(InstructionNotFoundInRow): removed_instruction.index_of_last_consumed_mesh_in_row def test_instruction_is_in_row(instruction0): assert instruction0.is_in_row() def test_instruction_is_not_in_row(removed_instruction): assert not removed_instruction.is_in_row() def test_repr_removed_instruction(removed_instruction): assert removed_instruction.__class__.__name__ in repr(removed_instruction) def test_repr_meshes(instruction0, row2): assert "Mesh" in repr(instruction0.produced_meshes[0]) assert "Mesh" in repr(instruction0.consumed_meshes[0]) assert "Mesh" in repr(row2.produced_meshes[0]) class TestShortAccess(object): """Test convenience methods and properties.""" def test_first_instruction(self, a1): for row in a1.rows: assert row.first_instruction == row.instructions[0] def test_last_instruction(self, a1): for row in a1.rows: assert row.last_instruction == row.instructions[-1] class TestInstructionColors(object): """Test Row.instruction_colors.""" @pytest.mark.parametrize("specs,result", [ ([{}, {}], [None]), ([{"color": 1}], [1]), ([{"color": 3}, {}, {"color": 123}, {"color": 123}], [3, None, 123])]) @pytest.mark.parametrize("row_spec,default_color", [ ({"color": "green"}, "green"), ({}, None)]) def test_row_instructions(self, specs, result, row_spec, default_color): result = result[:] for i, color in enumerate(result): if color is None: result[i] = default_color row = Row("id", row_spec, default_parser()) for instruction in specs: row.instructions.append(instruction) assert row.instruction_colors == result ================================================ FILE: knittingpattern/test/test_row_mapping.py ================================================ """Test that the rows of a pattern map the right way.""" from pytest import fixture from knittingpattern import load_from_object from knittingpattern.Loader import JSONLoader as Loader relative_path = "pattern/row_mapping_pattern.json" row_mapping_pattern1 = Loader().relative_file(__name__, relative_path) @fixture def p1(): return load_from_object(row_mapping_pattern1) @fixture def a1(p1): return p1.patterns["A.1"] @fixture def r11(a1): return a1.rows["1.1"] @fixture def r21(a1): return a1.rows["2.1"] @fixture def r22(a1): return a1.rows["2.2"] @fixture def r32(a1): return a1.rows["3.2"] @fixture def r41(a1): return a1.rows["4.1"] # TODO: test _get_producing_row_and_index def assert_rows_map(row1, index1, row2, index2): produced_mesh = row1.produced_meshes[index1] consumed_mesh = row2.consumed_meshes[index2] assert produced_mesh.is_connected_to(consumed_mesh) def assert_is_not_connected(row, index): assert not row.produced_meshes[index].is_connected() class TestRow11: def test_first_meshes_map_to_second_row(self, r11, r21): assert_rows_map(r11, 0, r21, 0) assert_rows_map(r11, 1, r21, 1) def test_middle_mesh_does_not_map_to_any_row(self, r11): assert_is_not_connected(r11, 2) def test_right_meshes_map_to_third_row(self, r11, r22): assert_rows_map(r11, 3, r22, 0) assert_rows_map(r11, 4, r22, 1) def test_number_of_meshes(self, r11): assert r11.number_of_produced_meshes == 5 assert r11.number_of_consumed_meshes == 4 class TestRow21: def test_all_meshes_map_to_last_row(self, r21, r41): assert_rows_map(r21, 0, r41, 0) assert_rows_map(r21, 1, r41, 1) def test_number_of_meshes(self, r21): assert r21.number_of_produced_meshes == 2 assert r21.number_of_consumed_meshes == 2 class TestRow22: def test_all_meshes_map_to_row_3(self, r22, r32): assert_rows_map(r22, 0, r32, 0) assert_rows_map(r22, 1, r32, 1) class TestRow32: def test_all_meshes_map_to_last_row(self, r32, r41): assert_rows_map(r32, 0, r41, 3) assert_rows_map(r32, 1, r41, 4) class TestRow41: def test_row_maps_to_nowhere(self, r41): for i in range(4): assert_is_not_connected(r41, i) def test_number_of_meshes(self, r41): assert r41.number_of_produced_meshes == 4 assert r41.number_of_consumed_meshes == 5 ================================================ FILE: knittingpattern/test/test_row_meshes.py ================================================ from pytest import fixture, raises from knittingpattern import new_knitting_pattern NO_CONSUMED_MESH = {"number of consumed meshes": 0} NO_PRODUCED_MESH = {"number of produced meshes": 0} DOUBLE_CONSUMED_MESH = {"number of consumed meshes": 2} DOUBLE_PRODUCED_MESH = {"number of produced meshes": 2} def assert_consumed_index(mesh, instruction_index, index_in_instruction=0): assert mesh.consuming_instruction.index_in_row == instruction_index assert mesh.index_in_consuming_instruction == index_in_instruction def assert_produced_index(mesh, instruction_index, index_in_instruction=0): assert mesh.producing_instruction.index_in_row == instruction_index assert mesh.index_in_producing_instruction == index_in_instruction def assert_row(row, first_consumed, last_consumed, first_produced, last_produced): assert_consumed_index(row.first_consumed_mesh, *first_consumed) assert_consumed_index(row.last_consumed_mesh, *last_consumed) assert_produced_index(row.first_produced_mesh, *first_produced) assert_produced_index(row.last_produced_mesh, *last_produced) @fixture def row(): pattern = new_knitting_pattern("test") return pattern.add_row(1) def test_no_meshes(row): with raises(IndexError): row.first_consumed_mesh with raises(IndexError): row.last_consumed_mesh with raises(IndexError): row.first_produced_mesh with raises(IndexError): row.last_produced_mesh def test_knit_row(row): row.instructions.extend([{}, {}, {}, {}]) assert_row(row, (0,), (3,), (0,), (3,)) def test_1_or_0(row): row.instructions.extend([NO_CONSUMED_MESH, {}, NO_PRODUCED_MESH]) assert_row(row, (1,), (2,), (0,), (1,)) def test_2(row): row.instructions.extend([DOUBLE_CONSUMED_MESH, {}, DOUBLE_PRODUCED_MESH]) assert_row(row, (0,), (2,), (0,), (2, 1)) def test_2_reversed(row): row.instructions.extend([DOUBLE_PRODUCED_MESH, {}, DOUBLE_CONSUMED_MESH]) assert_row(row, (0,), (2, 1), (0,), (2,)) ================================================ FILE: knittingpattern/test/test_utilities.py ================================================ from knittingpattern.utils import unique import pytest class TestUniquenes(object): """Test the function unique.""" @pytest.mark.parametrize("input,expected_result", [ ([], []), ([[1, 1, 1, 1, 1]], [1]), ([[1, 2, 3], [4, 3, 2, 1]], [1, 2, 3, 4]), ([[None, 4], [4, 6, None]], [None, 4, 6])]) @pytest.mark.parametrize("use_generator", [True, False]) def test_results(self, input, expected_result, use_generator): if use_generator: input = [(element for element in listing) for listing in input] result = unique(input) assert result == expected_result ================================================ FILE: knittingpattern/test/test_walk.py ================================================ """The the ability to sort rows in an order so they can be knit.""" import pytest from knittingpattern import load_from_relative_file, new_knitting_pattern from knittingpattern.walk import walk def walk_ids(pattern): return list(map(lambda row: row.id, walk(pattern))) @pytest.mark.parametrize("pattern_file,expected_ids", [ ("inheritance.json", ["colored", "inherited uncolored +instructions", "inherited colored +instructions", "uncolored", "inherited uncolored", "inherited colored"]), ("row_mapping_pattern.json", ["1.1", "2.1", "2.2", "3.2", "4.1"]), ("row_removal_pattern.json", [1, 2, 3]), ("single_instruction.json", [1, 2])]) def test_test_patterns(pattern_file, expected_ids): patterns = load_from_relative_file(__name__, "pattern/" + pattern_file) pattern = patterns.patterns.at(0) walked_ids = walk_ids(pattern) assert walked_ids == expected_ids def construct_graph(links): pattern = new_knitting_pattern("constructed_graph") rows = pattern.rows for link in links: for row_id in link: if row_id not in rows: pattern.add_row(row_id) for from_id, *to_ids in links: from_row = rows[from_id] for to_id in to_ids: to_row = rows[to_id] from_row.instructions.append({}) to_row.instructions.append({}) from_row.last_produced_mesh.connect_to(to_row.last_consumed_mesh) return pattern @pytest.mark.parametrize("links,expected_ids", [ (((1, 2, 3), (2, 3), (4, 1)), [4, 1, 2, 3]), (((4, 1, 2), (2, 3, 5), (5, 6), (3, 0), (0, 6)), [4, 1, 2, 3, 0, 5, 6]), (((8, 6, 4, 2, 0), (6, 5), (4, 5), (2, 1), (0, 1), (1, 7), (5, 7), (7, 9)), [8, 6, 4, 5, 2, 0, 1, 7, 9]), (((3, 1, 2), (1, 1.1), (1.1, 1.2), (2, 2.1), (2.1, 2.2), (2.2, 4), (1.2, 4)), [3, 1, 1.1, 1.2, 2, 2.1, 2.2, 4])]) def test_graphs_are_sorted(links, expected_ids): pattern = construct_graph(links) walked_ids = walk_ids(pattern) assert walked_ids == expected_ids ================================================ FILE: knittingpattern/utils.py ================================================ """This module contains some useful functions. The functions work on the standard library or are not specific to a certain existing module. """ def unique(iterables): """Create an iterable from the iterables that contains each element once. :return: an iterable over the iterables. Each element of the result appeared only once in the result. They are ordered by the first occurrence in the iterables. """ included_elements = set() def included(element): result = element in included_elements included_elements.add(element) return result return [element for elements in iterables for element in elements if not included(element)] __all__ = ["unique"] ================================================ FILE: knittingpattern/walk.py ================================================ """Walk the knitting pattern.""" def walk(knitting_pattern): """Walk the knitting pattern in a right-to-left fashion. :return: an iterable to walk the rows :rtype: list :param knittingpattern.KnittingPattern.KnittingPattern knitting_pattern: a knitting pattern to take the rows from """ rows_before = {} # key consumes from values free_rows = [] walk = [] for row in knitting_pattern.rows: rows_before_ = row.rows_before[:] if rows_before_: rows_before[row] = rows_before_ else: free_rows.append(row) assert free_rows while free_rows: # print("free rows:", free_rows) row = free_rows.pop(0) walk.append(row) assert row not in rows_before for freed_row in reversed(row.rows_after): todo = rows_before[freed_row] # print(" freed:", freed_row, todo) todo.remove(row) if not todo: del rows_before[freed_row] free_rows.insert(0, freed_row) assert not rows_before, "everything is walked" return walk __all__ = ["walk"] ================================================ FILE: pylintrc ================================================ [MASTER] # Specify a configuration file. #rcfile= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Add files or directories to the blacklist. They should be base names, not # paths. ignore=CVS # Pickle collected data for later comparisons. persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= # Use multiple processes to speed up Pylint. jobs=1 # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code extension-pkg-whitelist= # Allow optimization of some AST trees. This will activate a peephole AST # optimizer, which will apply various small optimizations. For instance, it can # be used to obtain the result of joining multiple strings with the addition # operator. Joining a lot of strings can lead to a maximum recursion error in # Pylint and this flag can prevent that. It has one side effect, the resulting # AST will be different than the one from reality. optimize-ast=no [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED confidence=HIGH # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. #enable= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once).You can also use "--disable=all" to # disable everything first and then reenable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" #disable=long-suffix,dict-view-method,hex-method,cmp-builtin,unicode-builtin,suppressed-message,old-division,execfile-builtin,file-builtin,getslice-method,indexing-exception,backtick,map-builtin-not-iterating,unichr-builtin,next-method-called,intern-builtin,old-raise-syntax,setslice-method,basestring-builtin,standarderror-builtin,delslice-method,long-builtin,useless-suppression,zip-builtin-not-iterating,reload-builtin,metaclass-assignment,coerce-method,raw_input-builtin,nonzero-method,reduce-builtin,dict-iter-method,apply-builtin,filter-builtin-not-iterating,cmp-method,round-builtin,input-builtin,coerce-builtin,range-builtin-not-iterating,old-octal-literal,buffer-builtin,unpacking-in-except,using-cmp-argument,raising-string,parameter-unpacking,oct-method,print-statement,import-star-module-level,old-ne-operator,xrange-builtin,no-absolute-import disable= [REPORTS] # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html. You can also give a reporter class, eg # mypackage.mymodule.MyReporterClass. output-format=text # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". files-output=no # Tells whether to display a full report or only the messages reports=yes # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details #msg-template= [BASIC] # List of builtins function names that should not be used, separated by a comma bad-functions=map,filter # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_,x,y # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata,temp # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= # Include a hint for the correct naming format with invalid-name include-naming-hint=yes # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Naming hint for module names module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression matching correct method names method-rgx=[a-z_][a-z0-9_]{2,70}$ # Naming hint for method names method-name-hint=[a-z_][a-z0-9_]{2,70}$ # Regular expression matching correct constant names const-rgx=((?P([A-Z_][A-Z0-9_]*)|(__.*__))|(?P([a-z_][a-z0-9_]*)|(__.*__)))$ # Naming hint for constant names const-name-hint=((?P([A-Z_][A-Z0-9_]*)|(__.*__))|(?P([a-z_][a-z0-9_]*)|(__.*__)))$ # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,70}|(__.*__))$ # Naming hint for class attribute names class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,70}|(__.*__))$ # Regular expression matching correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Naming hint for class names class-name-hint=[A-Z_][a-zA-Z0-9]+$ # Regular expression matching correct attribute names attr-rgx=[a-z_][a-z0-9_]{2,70}$ # Naming hint for attribute names attr-name-hint=[a-z_][a-z0-9_]{2,70}$ # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Naming hint for inline iteration names inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ # Regular expression matching correct function names function-rgx=[a-z_][a-z0-9_]{2,70}$ # Naming hint for function names function-name-hint=[a-z_][a-z0-9_]{2,70}$ # Regular expression matching correct argument names argument-rgx=[a-z_][a-z0-9_]{2,70}$ # Naming hint for argument names argument-name-hint=[a-z_][a-z0-9_]{2,70}$ # Regular expression matching correct variable names variable-rgx=[a-z_][a-z0-9_]{2,70}$ # Naming hint for variable names variable-name-hint=[a-z_][a-z0-9_]{2,70}$ # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=-1 [ELIF] # Maximum number of nested blocks for function / method body max-nested-blocks=5 [FORMAT] # Maximum number of characters on a single line. max-line-length=79 # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. # `trailing-comma` allows a space between comma and closing bracket: (a, ). # `empty-line` allows space-only lines. no-space-check=trailing-comma,dict-separator # Maximum number of lines in a module max-module-lines=1000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format= [LOGGING] # Logging modules to check that the string format arguments are in logging # function parameter format logging-modules=logging [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME,XXX,TODO [SIMILARITIES] # Minimum lines number of a similarity. min-similarity-lines=4 # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=no [SPELLING] # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package. spelling-dict= # List of comma separated words that should not be checked. spelling-ignore-words= # A path to a file that contains private dictionary; one word per line. spelling-private-dict-file= # Tells whether to store unknown words to indicated private dictionary in # --spelling-private-dict-file option instead of raising a message. spelling-store-unknown-words=no [TYPECHECK] # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It # supports qualified module names, as well as Unix pattern matching. ignored-modules= # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamically set). This supports can work # with qualified names. ignored-classes= # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members= [VARIABLES] # Tells whether we should check for unused import in __init__ files. init-import=no # A regular expression matching the name of dummy variables (i.e. expectedly # not used). dummy-variables-rgx=_$|dummy # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_,_cb [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict,_fields,_replace,_source,_make [DESIGN] # Maximum number of arguments for function / method max-args=5 # Argument names that match this expression will be ignored. Default to name # with leading underscore ignored-argument-names=_.* # Maximum number of locals for function / method body max-locals=15 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of branch for function / method body max-branches=12 # Maximum number of statements in function / method body max-statements=50 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Minimum number of public methods for a class (see R0903). min-public-methods=2 # Maximum number of public methods for a class (see R0904). max-public-methods=20 # Maximum number of boolean expressions in a if statement max-bool-expr=5 [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules=optparse # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "Exception" overgeneral-exceptions=Exception ================================================ FILE: requirements.in ================================================ setuptools Pillow webcolors xmltodict ================================================ FILE: requirements.txt ================================================ # # This file is autogenerated by pip-compile # To update, run: # # pip-compile --output-file requirements.txt requirements.in # appdirs==1.4.2 # via setuptools packaging==16.8 # via setuptools pillow==3.2.0 pyparsing==2.1.10 # via packaging six==1.10.0 # via packaging, setuptools webcolors==1.5 xmltodict==0.10.2 ObservableList==0.0.3 # The following packages are considered to be unsafe in a requirements file: # setuptools ================================================ FILE: setup.cfg ================================================ [upload_docs] upload-dir=build/html [build_sphinx] source-dir = docs/ build-dir = build/ all_files = 1 [upload_sphinx] upload-dir = build/html [pytest] # see https://pypi.python.org/pypi/pytest-flakes flakes-ignore = test_*.py UnusedImport RedefinedWhileUnused *.py ================================================ FILE: setup.py ================================================ #!/usr/bin/python3 """The setup and build script for the library named "PACKAGE_NAME".""" import os import sys from setuptools.command.test import test as TestCommandBase from distutils.core import Command import subprocess PACKAGE_NAME = "knittingpattern" PACKAGE_NAMES = [ "knittingpattern", "knittingpattern.convert", "knittingpattern.convert.test" ] HERE = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, HERE) # for package import __version__ = __import__(PACKAGE_NAME).__version__ __author__ = 'Nicco Kunzmann' def read_file_named(file_name): file_path = os.path.join(HERE, file_name) with open(file_path) as file: return file.read() def read_requirements_file(file_name): content = read_file_named(file_name) lines = [] for line in content.splitlines(): comment_index = line.find("#") if comment_index >= 0: line = line[:comment_index] line = line.strip() if not line: continue lines.append(line) return lines # The base package metadata to be used by both distutils and setuptools METADATA = dict( name=PACKAGE_NAME, version=__version__, packages=PACKAGE_NAMES, author=__author__, author_email='niccokunzmann@rambler.ru', description='Python library for knitting machines.', license='LGPL', url='https://github.com/fossasia/' + PACKAGE_NAME, keywords='knitting ayab fashion', ) # Run tests in setup class TestCommand(TestCommandBase): TEST_ARGS = [PACKAGE_NAME] def finalize_options(self): TestCommandBase.finalize_options(self) self.test_suite = False def run_tests(self): import pytest errcode = pytest.main(self.TEST_ARGS) sys.exit(errcode) class CoverageTestCommand(TestCommand): TEST_ARGS = [PACKAGE_NAME, "--cov=" + PACKAGE_NAME] class PEP8TestCommand(TestCommand): TEST_ARGS = [PACKAGE_NAME, "--pep8"] class FlakesTestCommand(TestCommand): TEST_ARGS = [PACKAGE_NAME, "--flakes"] class CoveragePEP8TestCommand(TestCommand): TEST_ARGS = [PACKAGE_NAME, "--cov=" + PACKAGE_NAME, "--pep8"] class LintCommand(TestCommandBase): TEST_ARGS = [PACKAGE_NAME] def finalize_options(self): TestCommandBase.finalize_options(self) self.test_suite = False def run_tests(self): from pylint.lint import Run Run(self.TEST_ARGS) # command for linking class LinkIntoSitePackagesCommand(Command): description = "link this module into the site-packages so the latest "\ "version can always be used without installation." user_options = [] library_path = os.path.join(HERE, PACKAGE_NAME) site_packages = [p for p in sys.path if "site-packages" in p] def initialize_options(self): pass def finalize_options(self): pass def run(self): assert self.site_packages, "We need a folder to install to." print("link: {} -> {}".format( os.path.join(self.site_packages[0], PACKAGE_NAME), self.library_path )) try: if "win" in sys.platform: self.run_windows_link() elif "linux" == sys.platform: self.run_linux_link() else: self.run_other_link() except: print("failed:") raise else: print("linked") def run_linux_link(self): subprocess.check_call(["sudo", "ln", "-f", "-s", "-t", self.site_packages[0], self.library_path]) run_other_link = run_linux_link def run_windows_link(self): path = os.path.join(self.site_packages[0], PACKAGE_NAME) if os.path.exists(path): os.remove(path) command = ["mklink", "/J", path, self.library_path] subprocess.check_call(command, shell=True) # Extra package metadata to be used only if setuptools is installed required_packages = read_requirements_file("requirements.txt") required_test_packages = read_requirements_file("test-requirements.txt") # print requirements class PrintRequiredPackagesCommand(Command): description = "Print the packages to install. "\ "Use pip install `setup.py requirements`" user_options = [] name = "requirements" def initialize_options(self): pass def finalize_options(self): pass @staticmethod def run(): packages = list(set(required_packages + required_test_packages)) packages.sort(key=lambda s: s.lower()) for package in packages: print(package) # set development status from __version__ DEVELOPMENT_STATES = { "p": "Development Status :: 1 - Planning", "pa": "Development Status :: 2 - Pre-Alpha", "a": "Development Status :: 3 - Alpha", "b": "Development Status :: 4 - Beta", "": "Development Status :: 5 - Production/Stable", "m": "Development Status :: 6 - Mature", "i": "Development Status :: 7 - Inactive" } development_state = DEVELOPMENT_STATES[""] for ending in DEVELOPMENT_STATES: if ending and __version__.endswith(ending): development_state = DEVELOPMENT_STATES[ending] if not __version__[-1:].isdigit(): METADATA["version"] += "0" # tag and upload to github to autodeploy with travis class TagAndDeployCommand(Command): description = "Create a git tag for this version and push it to origin."\ "To trigger a travis-ci build and and deploy." user_options = [] name = "tag_and_deploy" remote = "origin" branch = "master" def initialize_options(self): pass def finalize_options(self): pass def run(self): if subprocess.call(["git", "--version"]) != 0: print("ERROR:\n\tPlease install git.") exit(1) status_lines = subprocess.check_output(["git", "status"]).splitlines() current_branch = status_lines[0].strip().split()[-1].decode() print("On branch {}.".format(current_branch)) if current_branch != self.branch: print("ERROR:\n\tNew tags can only be made from branch \"{}\"." "".format(self.branch)) print("\tYou can use \"git checkout {}\" to switch the branch." "".format(self.branch)) exit(1) tags_output = subprocess.check_output(["git", "tag"]) tags = [tag.strip().decode() for tag in tags_output.splitlines()] tag = "v" + __version__ if tag in tags: print("Warning: \n\tTag {} already exists.".format(tag)) print("\tEdit the version information in {}".format( os.path.join(HERE, PACKAGE_NAME, "__init__.py") )) else: print("Creating tag \"{}\".".format(tag)) subprocess.check_call(["git", "tag", tag]) print("Pushing tag \"{}\" to remote \"{}\".".format(tag, self.remote)) subprocess.check_call(["git", "push", self.remote, tag]) SETUPTOOLS_METADATA = dict( install_requires=required_packages, tests_require=required_test_packages, include_package_data=True, classifiers=[ # https://pypi.python.org/pypi?%3Aaction=list_classifiers 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU Lesser General Public License' ' v3 (LGPLv3)', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Artistic Software', 'Topic :: Home Automation', 'Topic :: Utilities', 'Intended Audience :: Manufacturing', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3 :: Only', development_state ], package_data=dict( # If any package contains of these files, include them: knitting=['*.json'], ), zip_safe=False, cmdclass={ "test": TestCommand, "coverage": CoverageTestCommand, "coverage_test": CoverageTestCommand, "pep8": PEP8TestCommand, "pep8_test": PEP8TestCommand, "flakes": FlakesTestCommand, "fakes_test": FlakesTestCommand, "coverage_pep8_test": CoveragePEP8TestCommand, "lint": LintCommand, "link": LinkIntoSitePackagesCommand, PrintRequiredPackagesCommand.name: PrintRequiredPackagesCommand, TagAndDeployCommand.name: TagAndDeployCommand }, ) def main(): # Build the long_description from the README and CHANGES METADATA['long_description'] = read_file_named("README.rst") # Use setuptools if available, otherwise fallback and use distutils try: import setuptools METADATA.update(SETUPTOOLS_METADATA) setuptools.setup(**METADATA) except ImportError: import distutils.core distutils.core.setup(**METADATA) if __name__ == '__main__': if len(sys.argv) == 2 and sys.argv[1] == PrintRequiredPackagesCommand.name: PrintRequiredPackagesCommand.run() else: main() ================================================ FILE: test-requirements.in ================================================ pytest pytest-cov pytest-flakes pylint pytest-pep8 codeclimate-test-reporter untangle sphinx sphinx-paramlinks sphinx_rtd_theme ================================================ FILE: test-requirements.txt ================================================ # # This file is autogenerated by pip-compile # To update, run: # # pip-compile --output-file test-requirements.txt test-requirements.in # alabaster==0.7.8 # via sphinx apipkg==1.4 # via execnet astroid==1.4.6 # via pylint babel==2.3.4 # via sphinx codeclimate-test-reporter==0.1.1 colorama==0.3.7 # via pylint coverage==4.1 # via codeclimate-test-reporter, pytest-cov docutils==0.12 # via sphinx execnet==1.4.1 # via pytest-cache imagesize==0.7.1 # via sphinx jinja2==2.8 # via sphinx lazy-object-proxy==1.2.2 # via astroid markupsafe==0.23 # via jinja2 pep8==1.7.0 # via pytest-pep8 py==1.4.31 # via pytest pyflakes==1.2.3 # via pytest-flakes pygments==2.1.3 # via sphinx pylint==1.5.5 pytest-cache==1.0 # via pytest-flakes, pytest-pep8 pytest-cov==2.2.1 pytest-flakes==1.0.1 pytest-pep8==1.0.6 pytest==2.9.1 pytz==2016.4 # via babel requests==2.10.0 # via codeclimate-test-reporter six==1.10.0 # via astroid, pylint, sphinx snowballstemmer==1.2.1 # via sphinx sphinx-paramlinks==0.3.2 sphinx-rtd-theme==0.1.9 sphinx==1.4.4 untangle==1.1.0 wrapt==1.10.8 # via astroid