├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── pythonapp.yml ├── .gitignore ├── AUTHORS.txt ├── CHANGELOG ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── doc ├── Makefile ├── make.bat └── source │ ├── api.rst │ ├── conf.py │ ├── examples.rst │ ├── flower.rst │ ├── getting_started.rst │ ├── index.rst │ └── installation.rst ├── filebrowser.png ├── kivy_garden └── filebrowser │ ├── __init__.py │ ├── _version.py │ └── tests │ └── test_import.py ├── screenshot.png ├── setup.py └── tools └── hooks └── pre-commit /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Code example showing the issue: 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Logs/output** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Platform (please complete the following information):** 23 | - OS: [e.g. windows 10 /OSX 10.12 /linux/android 8/IOS 12…] 24 | - Python version. 25 | - release or git branch/commit 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/pythonapp.yml: -------------------------------------------------------------------------------- 1 | name: Garden flower 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | lint_test: 6 | runs-on: ubuntu-18.04 7 | steps: 8 | - uses: actions/checkout@v1 9 | - name: Set up Python 3.x 10 | uses: actions/setup-python@v1 11 | with: 12 | python-version: 3.x 13 | - name: Install dependencies 14 | run: | 15 | python3 -m pip install --upgrade pip virtualenv wheel setuptools 16 | - name: Lint with pycodestyle 17 | run: | 18 | python3 -m pip install flake8 19 | python3 -m flake8 . --count --ignore=E125,E126,E127,E128,E402,E741,E731,W503,F401,W504,F841 --show-source --statistics --max-line-length=80 --exclude=__pycache__,.tox,.git/,doc/ 20 | 21 | linux_test: 22 | runs-on: ubuntu-18.04 23 | env: 24 | DISPLAY: :99.0 25 | steps: 26 | - uses: actions/checkout@v1 27 | - name: Set up Python 3.x 28 | uses: actions/setup-python@v1 29 | with: 30 | python-version: 3.x 31 | - name: Make sdist 32 | run: python3 setup.py sdist --formats=gztar 33 | - name: Install dependencies 34 | run: | 35 | sudo apt update 36 | sudo apt -y install libsdl2-dev libsdl2-ttf-dev libsdl2-image-dev libsdl2-mixer-dev 37 | sudo apt-get -y install python3-setuptools build-essential libgl1-mesa-dev libgles2-mesa-dev 38 | /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1280x720x24 -ac +extension GLX 39 | 40 | python3 -m pip install --upgrade pip virtualenv wheel setuptools 41 | python3 -m pip install kivy[base] --pre --extra-index-url https://kivy.org/downloads/simple/ 42 | - name: Install flower 43 | run: python3 -m pip install -e .[dev,ci] --extra-index-url https://kivy-garden.github.io/simple/ 44 | - name: Make wheel 45 | run: python3 setup.py bdist_wheel --universal 46 | - name: Upload wheels as artifact 47 | uses: actions/upload-artifact@master 48 | with: 49 | name: wheels 50 | path: dist 51 | - name: Upload to GitHub Release 52 | uses: softprops/action-gh-release@78c309ef59fdb9557cd6574f2e0be552936ed728 53 | if: startsWith(github.ref, 'refs/tags/') 54 | env: 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | with: 57 | files: dist/* 58 | draft: true 59 | - name: Test with pytest 60 | run: | 61 | flower_name=$(python3 -c "print('$GITHUB_REPOSITORY'.split('/')[-1])") 62 | python3 -m pytest --cov=kivy_garden.$flower_name --cov-report term --cov-branch 63 | 64 | windows_test: 65 | runs-on: windows-latest 66 | env: 67 | KIVY_GL_BACKEND: angle_sdl2 68 | steps: 69 | - uses: actions/checkout@v1 70 | - name: Set up Python 3.x 71 | uses: actions/setup-python@v1 72 | with: 73 | python-version: 3.x 74 | - name: Install dependencies 75 | run: | 76 | python -m pip install --upgrade pip virtualenv wheel setuptools 77 | python -m pip install kivy[win_full] --pre --extra-index-url https://kivy.org/downloads/simple/ 78 | - name: Install flower 79 | run: python -m pip install -e .[dev,ci] --extra-index-url https://kivy-garden.github.io/simple/ 80 | - name: Test with pytest 81 | run: | 82 | $flower_name=(python -c "print('$GITHUB_REPOSITORY'.split('/')[-1])") 83 | python -m pytest --cov=kivy_garden.$flower_name --cov-report term --cov-branch 84 | 85 | docs: 86 | runs-on: ubuntu-18.04 87 | steps: 88 | - uses: actions/checkout@v1 89 | - name: Set up Python 3.x 90 | uses: actions/setup-python@v1 91 | with: 92 | python-version: 3.x 93 | - name: Install dependencies 94 | env: 95 | KIVY_DOC_INCLUDE: 1 96 | KIVY_DOC: 1 97 | run: | 98 | python3 -m pip install --upgrade pip virtualenv wheel setuptools sphinx 99 | python3 -m pip install kivy[base] --pre --extra-index-url https://kivy.org/downloads/simple/ 100 | python3 -m pip install -e .[dev,ci] --extra-index-url https://kivy-garden.github.io/simple/ 101 | - name: Generate docs 102 | run: | 103 | cd doc 104 | make html 105 | - name: Upload docs as artifact 106 | uses: actions/upload-artifact@master 107 | with: 108 | name: docs 109 | path: doc/build/html 110 | - name: gh-pages upload 111 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' 112 | env: 113 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 114 | run: | 115 | flower_name=$(python3 -c "print('$GITHUB_REPOSITORY'.split('/')[-1])") 116 | cp -r doc/build/html ~/docs_temp 117 | 118 | git config --global user.email "kivy@kivy.org" 119 | git config --global user.name "Kivy Developers" 120 | git remote rm origin || true 121 | git remote add origin "https://x-access-token:${GITHUB_TOKEN}@github.com/$GITHUB_REPOSITORY.git" 122 | 123 | git checkout --orphan gh-pages 124 | cp -r .git ~/docs_git 125 | cd .. 126 | rm -rf $flower_name 127 | mkdir $flower_name 128 | cd $flower_name 129 | cp -r ~/docs_git .git 130 | cp -r ~/docs_temp/* . 131 | touch .nojekyll 132 | 133 | git add . 134 | git commit -a -m "Docs for git-$GITHUB_SHA" 135 | git push origin gh-pages -f 136 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # IPython 79 | profile_default/ 80 | ipython_config.py 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # pipenv 86 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 87 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 88 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 89 | # install all needed dependencies. 90 | #Pipfile.lock 91 | 92 | # celery beat schedule file 93 | celerybeat-schedule 94 | 95 | # SageMath parsed files 96 | *.sage.py 97 | 98 | # Environments 99 | .env 100 | .venv 101 | env/ 102 | venv/ 103 | ENV/ 104 | env.bak/ 105 | venv.bak/ 106 | 107 | # Spyder project settings 108 | .spyderproject 109 | .spyproject 110 | 111 | # Rope project settings 112 | .ropeproject 113 | 114 | # mkdocs documentation 115 | /site 116 | 117 | # mypy 118 | .mypy_cache/ 119 | .dmypy.json 120 | dmypy.json 121 | 122 | # Pyre type checker 123 | .pyre/ 124 | -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivy-garden/filebrowser/6aec4058585bdacc5364f4ee267e694bcfde8589/AUTHORS.txt -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | v0.1.0 Initial release 2 | ====================== 3 | 4 | Added the demo flower project. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | CONTRIBUTING 2 | ============== 3 | 4 | This software is open source and welcomes open contributions, there are just 5 | a few guidelines, if you are unsure about them, please ask and guidance will be 6 | provided. 7 | 8 | - The code is hosted on GitHub and 9 | development happens here, using the tools provided by the platform. 10 | Contributions are accepted in the form of Pull Requests. Bugs are to be 11 | reported in the issue tracker provided there. 12 | 13 | - Please follow [PEP8](https://www.python.org/dev/peps/pep-0008/), hopefully 14 | your editor can be configured to automatically enforce it, but you can also 15 | install (using pip) and run `pycodestyle` from the command line, 16 | to get a report about it. 17 | 18 | - Avoid lowering the test coverage, it's hard to achieve 100%, but staying as 19 | close to it as possible is a good way to improve quality by catching bugs as 20 | early as possible. Tests are ran by Travis, and the coverage is 21 | evaluated by Coveralls, so you'll get a report about your contribution 22 | breaking any test, and the evolution of coverage, but you can also check that 23 | locally before sending the contribution, by using `pytest --cov-report 24 | term-missing --cov kivy_garden.flower`, you can also use `pytest --cov-report html --cov 25 | kivy_garden.flower` to get an html report that you can open in your browser. 26 | 27 | - Please try to conform to the style of the codebase, if you have a question, 28 | just ask. 29 | 30 | - Please keep performance in mind when editing the code, if you 31 | see room for improvement, share your suggestions by opening an issue, 32 | or open a pull request directly. 33 | 34 | - Please keep in mind that the code you contribute will be subject to the MIT 35 | license, don't include code if it's not under a compatible license, and you 36 | are not the copyright holder. 37 | 38 | Tips 39 | ------ 40 | 41 | You can install the package in `editable` mode, with the `dev` option, 42 | to easily have all the required tools to check your edits. 43 | 44 | pip install --editable .[dev] 45 | 46 | You can make sure the tests are ran before pushing by using the git hook. 47 | 48 | cp tools/hooks/pre-commit .git/hooks/ 49 | 50 | If you are unsure of the meaning of the pycodestyle output, you can use the 51 | --show-pep8 flag to learn more about the errors. 52 | 53 | pycodestyle --show-pep8 54 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019- Kivy Team and other contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Coverage Status](https://coveralls.io/repos/github/kivy-garden/filebrowser/badge.svg?branch=master)](https://coveralls.io/github/kivy-garden/filebrowser?branch=master) 2 | [![Github Build Status](https://github.com/kivy-garden/filebrowser/workflows/Garden%20flower/badge.svg)](https://github.com/kivy-garden/filebrowser/actions) 3 | 4 | See https://kivy-garden.github.io/filebrowser/ for the rendered filebrowser docs. 5 | 6 | For a native filebrowser, consider using [plyer's filechooser](https://github.com/kivy/plyer) instead. 7 | 8 | FileBrowser 9 | =========== 10 | 11 | The ``FileBrowser`` widget is an advanced file browser. You use it 12 | similarly to FileChooser usage. 13 | 14 | It provides a shortcut bar with links to special and system directories. 15 | When touching next to a shortcut in the links bar, it'll expand and show 16 | all the directories within that directory. It also facilitates specifying 17 | custom paths to be added to the shortcuts list. 18 | 19 | It provides a icon and list view to choose files from. And it also accepts 20 | filter and filename inputs. 21 | 22 | To create a ``FileBrowser`` which prints the currently selected file as 23 | well as the current text in the filename field when 'Select' is pressed, 24 | with a shortcut to the Documents directory added to the favorites bar: 25 | 26 | .. code-block:: python 27 | 28 | from kivy.app import App 29 | from os.path import sep, expanduser, isdir, dirname 30 | import sys 31 | 32 | class TestApp(App): 33 | 34 | def build(self): 35 | if sys.platform == 'win': 36 | user_path = dirname(expanduser('~')) + sep + 'Documents' 37 | else: 38 | user_path = expanduser('~') + sep + 'Documents' 39 | browser = FileBrowser(select_string='Select', 40 | favorites=[(user_path, 'Documents')]) 41 | browser.bind( 42 | on_success=self._fbrowser_success, 43 | on_canceled=self._fbrowser_canceled) 44 | return browser 45 | 46 | def _fbrowser_canceled(self, instance): 47 | print 'cancelled, Close self.' 48 | 49 | def _fbrowser_success(self, instance): 50 | print instance.selection 51 | 52 | TestApp().run() 53 | 54 | Events 55 | ------ 56 | 57 | - ``on_canceled`` 58 | Fired when the `Cancel` buttons `on_release` event is called. 59 | - ``on_success`` 60 | Fired when the `Select` buttons `on_release` event is called. 61 | 62 | 63 | 64 | Install 65 | --------- 66 | 67 | ```sh 68 | pip install kivy_garden.filebrowser 69 | ``` 70 | 71 | CI 72 | -- 73 | 74 | Every push or pull request run the [GitHub Action](https://github.com/kivy-garden/flower/actions) CI. 75 | It tests the code on various OS and also generates wheels that can be released on PyPI upon a 76 | tag. Docs are also generated and uploaded to the repo as well as artifacts of the CI. 77 | 78 | TODO 79 | ------- 80 | 81 | * add your code 82 | 83 | Contributing 84 | -------------- 85 | 86 | Check out our [contribution guide](CONTRIBUTING.md) and feel free to improve the flower. 87 | 88 | License 89 | --------- 90 | 91 | This software is released under the terms of the MIT License. 92 | Please see the [LICENSE.txt](LICENSE.txt) file. 93 | 94 | How to release 95 | =============== 96 | 97 | * update `__version__` in `kivy-garden/filebrowser/__init__.py` to the latest version. 98 | * update `CHANGELOG.md` and commit the changes 99 | * call `git tag -a x.y.z -m "Tagging version x.y.z"` 100 | * call `python setup.py bdist_wheel --universal` and `python setup.py sdist`, which generates the wheel and sdist in the dist/* directory 101 | * Make sure the dist directory contains the files to be uploaded to pypi and call `twine check dist/*` 102 | * then call `twine upload dist/*` to upload to pypi. 103 | * call `git push origin master --tags` to push the latest changes and the tags to github. 104 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /doc/source/api.rst: -------------------------------------------------------------------------------- 1 | 2 | ###################### 3 | The Filebrowser API 4 | ###################### 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | flower.rst 10 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # http://www.sphinx-doc.org/en/master/config 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sphinx_rtd_theme 15 | 16 | 17 | # -- Project information ----------------------------------------------------- 18 | 19 | project = 'Filebrowser' 20 | copyright = '2019, Kivy' 21 | author = 'Kivy' 22 | 23 | 24 | # -- General configuration --------------------------------------------------- 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be 27 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 28 | # ones. 29 | extensions = [ 30 | 'sphinx.ext.autodoc', 31 | 'sphinx.ext.todo', 32 | 'sphinx.ext.coverage', 33 | 'sphinx.ext.intersphinx', 34 | "sphinx_rtd_theme", 35 | ] 36 | 37 | intersphinx_mapping = { 38 | 'kivy': ('https://kivy.org/docs/', None), 39 | } 40 | 41 | # Add any paths that contain templates here, relative to this directory. 42 | templates_path = ['_templates'] 43 | 44 | # List of patterns, relative to source directory, that match files and 45 | # directories to ignore when looking for source files. 46 | # This pattern also affects html_static_path and html_extra_path. 47 | exclude_patterns = [] 48 | 49 | 50 | # -- Options for HTML output ------------------------------------------------- 51 | 52 | # The theme to use for HTML and HTML Help pages. See the documentation for 53 | # a list of builtin themes. 54 | # 55 | html_theme = 'sphinx_rtd_theme' 56 | 57 | # Add any paths that contain custom static files (such as style sheets) here, 58 | # relative to this directory. They are copied after the builtin static files, 59 | # so a file named "default.css" will overwrite the builtin "default.css". 60 | html_static_path = ['_static'] 61 | -------------------------------------------------------------------------------- /doc/source/examples.rst: -------------------------------------------------------------------------------- 1 | .. _examples: 2 | 3 | ******** 4 | Examples 5 | ******** 6 | 7 | 8 | Basic Example 9 | ------------- 10 | 11 | Add your examples here -------------------------------------------------------------------------------- /doc/source/flower.rst: -------------------------------------------------------------------------------- 1 | .. _flower-api: 2 | 3 | ************ 4 | Filebrowser 5 | ************ 6 | 7 | :mod:`kivy_garden.filebrowser` 8 | ============================= 9 | 10 | .. automodule:: kivy_garden.filebrowser 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | -------------------------------------------------------------------------------- /doc/source/getting_started.rst: -------------------------------------------------------------------------------- 1 | .. _started: 2 | 3 | #################### 4 | Getting Started 5 | #################### 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | installation.rst 11 | examples.rst 12 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Flower documentation master file, created by 2 | sphinx-quickstart on Wed Jun 19 14:32:58 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Filebrowser's documentation! 7 | ======================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | getting_started.rst 14 | api.rst 15 | 16 | 17 | 18 | Indices and tables 19 | ================== 20 | 21 | * :ref:`genindex` 22 | * :ref:`modindex` 23 | * :ref:`search` 24 | -------------------------------------------------------------------------------- /doc/source/installation.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | ************ 4 | Installation 5 | ************ 6 | 7 | Supported versions 8 | ------------------ 9 | 10 | * Python 3.5+ 11 | 12 | Dependencies 13 | ------------ 14 | 15 | #. `Kivy `_ 16 | 17 | 18 | Installation 19 | ------------ 20 | 21 | Please see the `garden docs `_ for full installation instructions. 22 | 23 | If the filebrowser maintainer has uploaded the flower to 24 | `pypi `_, you can just install it with 25 | `pip install kivy_garden.filebrowser`. 26 | 27 | You can install filebrowser master directly from github with:: 28 | 29 | python -m pip install https://github.com/kivy-garden/filebrowser/archive/master.zip 30 | 31 | Look under the repository's releases tab if you'd like to install a specific 32 | release or a pre-compiled wheel, if the flower has any. Then use the url with 33 | `pip`. 34 | 35 | Or you can automatically install it using garden's pypi server with:: 36 | 37 | python -m pip install kivy_garden.filebrowser --extra-index-url https://kivy-garden.github.io/simple/ 38 | 39 | To permanently add our garden server to your pip configuration so that you 40 | don't have to specify it with `--extra-index-url`, add:: 41 | 42 | [global] 43 | timeout = 60 44 | index-url = https://kivy-garden.github.io/simple/ 45 | 46 | to your `pip.conf `_. 47 | 48 | -------------------------------------------------------------------------------- /filebrowser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivy-garden/filebrowser/6aec4058585bdacc5364f4ee267e694bcfde8589/filebrowser.png -------------------------------------------------------------------------------- /kivy_garden/filebrowser/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | FileBrowser 3 | =========== 4 | 5 | The :class:`FileBrowser` widget is an advanced file browser. You use it 6 | similarly to FileChooser usage. 7 | 8 | It provides a shortcut bar with links to special and system directories. 9 | When touching next to a shortcut in the links bar, it'll expand and show 10 | all the directories within that directory. It also facilitates specifying 11 | custom paths to be added to the shortcuts list. 12 | 13 | It provides a icon and list view to choose files from. And it also accepts 14 | filter and filename inputs. 15 | 16 | To create a FileBrowser which prints the currently selected file as well as 17 | the current text in the filename field when 'Select' is pressed, with 18 | a shortcut to the Documents directory added to the favorites bar:: 19 | 20 | import os 21 | from kivy.app import App 22 | 23 | class TestApp(App): 24 | 25 | def build(self): 26 | user_path = os.path.join(get_home_directory(), 'Documents') 27 | browser = FileBrowser(select_string='Select', 28 | favorites=[(user_path, 'Documents')]) 29 | browser.bind(on_success=self._fbrowser_success, 30 | on_canceled=self._fbrowser_canceled, 31 | on_submit=self._fbrowser_submit) 32 | return browser 33 | 34 | def _fbrowser_canceled(self, instance): 35 | print('cancelled, Close self.') 36 | 37 | def _fbrowser_success(self, instance): 38 | print(instance.selection) 39 | 40 | def _fbrowser_submit(self, instance): 41 | print(instance.selection) 42 | 43 | TestApp().run() 44 | 45 | :Events: 46 | `on_canceled`: 47 | Fired when the `Cancel` buttons `on_release` event is called. 48 | 49 | `on_success`: 50 | Fired when the `Select` buttons `on_release` event is called. 51 | 52 | `on_submit`: 53 | Fired when a file has been selected with a double-tap. 54 | 55 | .. image:: _static/filebrowser.png 56 | :align: right 57 | ''' 58 | 59 | from kivy.uix.boxlayout import BoxLayout 60 | from kivy.uix.treeview import TreeViewLabel, TreeView 61 | try: 62 | from kivy.garden.filechooserthumbview import FileChooserThumbView as \ 63 | IconView 64 | except (ImportError, KeyError): 65 | from kivy.uix.filechooser import FileChooserIconView as IconView 66 | from kivy.properties import (ObjectProperty, StringProperty, OptionProperty, 67 | ListProperty, BooleanProperty) 68 | from kivy.lang import Builder 69 | from kivy.utils import platform 70 | from kivy.clock import Clock 71 | from kivy.compat import PY2 72 | import string 73 | from os.path import sep, dirname, expanduser, isdir, join 74 | from os import walk 75 | from sys import getfilesystemencoding 76 | from functools import partial 77 | 78 | if platform == 'win': 79 | from ctypes import windll, create_unicode_buffer 80 | 81 | from ._version import __version__ 82 | 83 | __all__ = ('FileBrowser', ) 84 | 85 | 86 | def get_home_directory(): 87 | if platform == 'win': 88 | user_path = expanduser('~') 89 | 90 | if not isdir(join(user_path, 'Desktop')): 91 | user_path = dirname(user_path) 92 | 93 | else: 94 | user_path = expanduser('~') 95 | 96 | if PY2: 97 | user_path = user_path.decode(getfilesystemencoding()) 98 | 99 | return user_path 100 | 101 | 102 | def get_drives(): 103 | drives = [] 104 | if platform == 'win': 105 | bitmask = windll.kernel32.GetLogicalDrives() 106 | GetVolumeInformationW = windll.kernel32.GetVolumeInformationW 107 | for letter in string.ascii_uppercase: 108 | if bitmask & 1: 109 | name = create_unicode_buffer(64) 110 | # get name of the drive 111 | drive = letter + u':' 112 | res = GetVolumeInformationW(drive + sep, name, 64, None, 113 | None, None, None, 0) 114 | if isdir(drive): 115 | drives.append((drive, name.value)) 116 | bitmask >>= 1 117 | elif platform == 'linux': 118 | drives.append((sep, sep)) 119 | drives.append((expanduser(u'~'), '~/')) 120 | places = (sep + u'mnt', sep + u'media') 121 | for place in places: 122 | if isdir(place): 123 | for directory in next(walk(place))[1]: 124 | drives.append((place + sep + directory, directory)) 125 | elif platform == 'macosx' or platform == 'ios': 126 | drives.append((expanduser(u'~'), '~/')) 127 | vol = sep + u'Volume' 128 | if isdir(vol): 129 | for drive in next(walk(vol))[1]: 130 | drives.append((vol + sep + drive, drive)) 131 | return drives 132 | 133 | 134 | class FileBrowserIconView(IconView): 135 | pass 136 | 137 | 138 | Builder.load_string(''' 139 | #:kivy 1.2.0 140 | #:import metrics kivy.metrics 141 | #:import abspath os.path.abspath 142 | 143 | : 144 | on_touch_down: 145 | self.parent.browser.current_tab.content.path = self.path if\ 146 | self.collide_point(*args[1].pos) and self.path else\ 147 | self.parent.browser.current_tab.content.path 148 | on_is_open: self.is_open and self.parent.trigger_populate(self) 149 | 150 | : 151 | orientation: 'vertical' 152 | spacing: 5 153 | padding: [6, 6, 6, 6] 154 | select_state: select_button.state 155 | cancel_state: cancel_button.state 156 | filename: file_text.text 157 | on_favorites: link_tree.reload_favs(self.favorites) 158 | BoxLayout: 159 | orientation: 'horizontal' 160 | spacing: 5 161 | Splitter: 162 | sizable_from: 'right' 163 | min_size: '153sp' 164 | size_hint: (.2, 1) 165 | id: splitter 166 | ScrollView: 167 | LinkTree: 168 | id: link_tree 169 | browser: tabbed_browser 170 | size_hint_y: None 171 | height: self.minimum_height 172 | on_parent: self.fill_tree(root.favorites) 173 | root_options: {'text': 'Locations', 'no_selection':True} 174 | BoxLayout: 175 | size_hint_x: .8 176 | orientation: 'vertical' 177 | Label: 178 | size_hint_y: None 179 | height: '22dp' 180 | text_size: self.size 181 | padding_x: '10dp' 182 | text: abspath(root.path) 183 | valign: 'middle' 184 | TabbedPanel: 185 | id: tabbed_browser 186 | do_default_tab: False 187 | TabbedPanelItem: 188 | text: 'List View' 189 | FileChooserListView: 190 | id: list_view 191 | path: root.path 192 | filters: root.filters 193 | filter_dirs: root.filter_dirs 194 | show_hidden: root.show_hidden 195 | multiselect: root.multiselect 196 | dirselect: root.dirselect 197 | rootpath: root.rootpath 198 | on_submit: root.dispatch('on_submit') 199 | TabbedPanelItem: 200 | text: 'Icon View' 201 | FileBrowserIconView: 202 | id: icon_view 203 | path: root.path 204 | filters: root.filters 205 | filter_dirs: root.filter_dirs 206 | show_hidden: root.show_hidden 207 | multiselect: root.multiselect 208 | dirselect: root.dirselect 209 | rootpath: root.rootpath 210 | on_submit: root.dispatch('on_submit') 211 | GridLayout: 212 | size_hint: (1, None) 213 | height: file_text.line_height * 4 214 | cols: 2 215 | rows: 2 216 | spacing: [5] 217 | TextInput: 218 | id: file_text 219 | text: (root.selection and (root._shorten_filenames(\ 220 | root.selection) if root.multiselect else root.selection[0])) or '' 221 | hint_text: 'Filename' 222 | multiline: False 223 | Button: 224 | id: select_button 225 | size_hint_x: None 226 | width: metrics.dp(100) 227 | text: root.select_string 228 | on_release: root.dispatch('on_success') 229 | TextInput: 230 | id: filt_text 231 | hint_text: '*.*' 232 | on_text_validate: 233 | root.filters = self.text.split(',') if self.text else [] 234 | multiline: False 235 | text: ','.join(\ 236 | [filt for filt in root.filters if isinstance(filt, str)]) 237 | Button: 238 | id: cancel_button 239 | size_hint_x: None 240 | width: metrics.dp(100) 241 | text: root.cancel_string 242 | on_release: root.dispatch('on_canceled') 243 | 244 | ''') 245 | 246 | 247 | class TreeLabel(TreeViewLabel): 248 | path = StringProperty('') 249 | '''Full path to the location this node points to. 250 | 251 | :class:`~kivy.properties.StringProperty`, defaults to '' 252 | ''' 253 | 254 | 255 | class LinkTree(TreeView): 256 | # link to the favorites section of link bar 257 | _favs = ObjectProperty(None) 258 | _computer_node = None 259 | 260 | def fill_tree(self, fav_list): 261 | user_path = get_home_directory() 262 | self._favs = self.add_node(TreeLabel(text='Favorites', is_open=True, 263 | no_selection=True)) 264 | self.reload_favs(fav_list) 265 | 266 | libs = self.add_node(TreeLabel(text='Libraries', is_open=True, 267 | no_selection=True)) 268 | places = ('Documents', 'Music', 'Pictures', 'Videos') 269 | for place in places: 270 | if isdir(join(user_path, place)): 271 | self.add_node(TreeLabel(text=place, path=join(user_path, 272 | place)), libs) 273 | self._computer_node = self.add_node(TreeLabel( 274 | text='Computer', is_open=True, no_selection=True)) 275 | self._computer_node.bind(on_touch_down=self._drives_touch) 276 | self.reload_drives() 277 | 278 | def _drives_touch(self, obj, touch): 279 | if obj.collide_point(*touch.pos): 280 | self.reload_drives() 281 | 282 | def reload_drives(self): 283 | nodes = [(node, node.text + node.path) 284 | for node in self._computer_node.nodes 285 | if isinstance(node, TreeLabel)] 286 | sigs = [s[1] for s in nodes] 287 | nodes_new = [] 288 | sig_new = [] 289 | for path, name in get_drives(): 290 | if platform == 'win': 291 | text = u'{}({})'.format((name + ' ') if name else '', path) 292 | else: 293 | text = name 294 | nodes_new.append((text, path)) 295 | sig_new.append(text + path + sep) 296 | for node, sig in nodes: 297 | if sig not in sig_new: 298 | self.remove_node(node) 299 | for text, path in nodes_new: 300 | if text + path + sep not in sigs: 301 | self.add_node(TreeLabel(text=text, path=path + sep), 302 | self._computer_node) 303 | 304 | def reload_favs(self, fav_list): 305 | user_path = get_home_directory() 306 | favs = self._favs 307 | remove = [] 308 | for node in self.iterate_all_nodes(favs): 309 | if node != favs: 310 | remove.append(node) 311 | for node in remove: 312 | self.remove_node(node) 313 | places = ('Desktop', 'Downloads') 314 | for place in places: 315 | if isdir(join(user_path, place)): 316 | self.add_node(TreeLabel(text=place, path=join(user_path, 317 | place)), favs) 318 | for path, name in fav_list: 319 | if isdir(path): 320 | self.add_node(TreeLabel(text=name, path=path), favs) 321 | 322 | def trigger_populate(self, node): 323 | if not node.path or node.nodes: 324 | return 325 | parent = node.path 326 | _next = next(walk(parent)) 327 | if _next: 328 | for path in _next[1]: 329 | self.add_node(TreeLabel(text=path, path=parent + sep + path), 330 | node) 331 | 332 | 333 | class FileBrowser(BoxLayout): 334 | '''FileBrowser class, see module documentation for more information. 335 | ''' 336 | 337 | __events__ = ('on_canceled', 'on_success', 'on_submit') 338 | 339 | select_state = OptionProperty('normal', options=('normal', 'down')) 340 | '''State of the 'select' button, must be one of 'normal' or 'down'. 341 | The state is 'down' only when the button is currently touched/clicked, 342 | otherwise 'normal'. This button functions as the typical Ok/Select/Save 343 | button. 344 | 345 | :data:`select_state` is an :class:`~kivy.properties.OptionProperty`. 346 | ''' 347 | cancel_state = OptionProperty('normal', options=('normal', 'down')) 348 | '''State of the 'cancel' button, must be one of 'normal' or 'down'. 349 | The state is 'down' only when the button is currently touched/clicked, 350 | otherwise 'normal'. This button functions as the typical cancel button. 351 | 352 | :data:`cancel_state` is an :class:`~kivy.properties.OptionProperty`. 353 | ''' 354 | 355 | select_string = StringProperty('Ok') 356 | '''Label of the 'select' button. 357 | 358 | :data:`select_string` is an :class:`~kivy.properties.StringProperty`, 359 | defaults to 'Ok'. 360 | ''' 361 | 362 | cancel_string = StringProperty('Cancel') 363 | '''Label of the 'cancel' button. 364 | 365 | :data:`cancel_string` is an :class:`~kivy.properties.StringProperty`, 366 | defaults to 'Cancel'. 367 | ''' 368 | 369 | filename = StringProperty('') 370 | '''The current text in the filename field. Read only. When multiselect is 371 | True, the list of selected filenames is shortened. If shortened, filename 372 | will contain an ellipsis. 373 | 374 | :data:`filename` is an :class:`~kivy.properties.StringProperty`, 375 | defaults to ''. 376 | 377 | .. versionchanged:: 1.1 378 | ''' 379 | 380 | selection = ListProperty([]) 381 | '''Read-only :class:`~kivy.properties.ListProperty`. 382 | Contains the list of files that are currently selected in the current tab. 383 | See :kivy_fchooser:`kivy.uix.filechooser.FileChooserController.selection`. 384 | 385 | .. versionchanged:: 1.1 386 | ''' 387 | 388 | path = StringProperty(u'/') 389 | ''' 390 | :class:`~kivy.properties.StringProperty`, defaults to the current working 391 | directory as a unicode string. It specifies the path on the filesystem that 392 | browser should refer to. 393 | See :kivy_fchooser:`kivy.uix.filechooser.FileChooserController.path`. 394 | 395 | .. versionadded:: 1.1 396 | ''' 397 | 398 | filters = ListProperty([]) 399 | ''':class:`~kivy.properties.ListProperty`, defaults to [], equal to 400 | ``'*'``. 401 | 402 | Specifies the filters to be applied to the files in the directory. 403 | See :kivy_fchooser:`kivy.uix.filechooser.FileChooserController.filters`. 404 | 405 | Filering keywords that the user types into the filter field as a comma 406 | separated list will be reflected here. 407 | 408 | .. versionadded:: 1.1 409 | ''' 410 | 411 | filter_dirs = BooleanProperty(False) 412 | ''' 413 | :class:`~kivy.properties.BooleanProperty`, defaults to False. 414 | Indicates whether filters should also apply to directories. 415 | See 416 | :kivy_fchooser:`kivy.uix.filechooser.FileChooserController.filter_dirs`. 417 | 418 | .. versionadded:: 1.1 419 | ''' 420 | 421 | show_hidden = BooleanProperty(False) 422 | ''' 423 | :class:`~kivy.properties.BooleanProperty`, defaults to False. 424 | Determines whether hidden files and folders should be shown. 425 | See 426 | :kivy_fchooser:`kivy.uix.filechooser.FileChooserController.show_hidden`. 427 | 428 | .. versionadded:: 1.1 429 | ''' 430 | 431 | multiselect = BooleanProperty(False) 432 | ''' 433 | :class:`~kivy.properties.BooleanProperty`, defaults to False. 434 | Determines whether the user is able to select multiple files or not. 435 | See 436 | :kivy_fchooser:`kivy.uix.filechooser.FileChooserController.multiselect`. 437 | 438 | .. versionadded:: 1.1 439 | ''' 440 | 441 | dirselect = BooleanProperty(False) 442 | ''' 443 | :class:`~kivy.properties.BooleanProperty`, defaults to False. 444 | Determines whether directories are valid selections or not. 445 | See 446 | :kivy_fchooser:`kivy.uix.filechooser.FileChooserController.dirselect`. 447 | 448 | .. versionadded:: 1.1 449 | ''' 450 | 451 | rootpath = StringProperty(None, allownone=True) 452 | ''' 453 | Root path to use instead of the system root path. If set, it will not show 454 | a ".." directory to go up to the root path. For example, if you set 455 | rootpath to /users/foo, the user will be unable to go to /users or to any 456 | other directory not starting with /users/foo. 457 | :class:`~kivy.properties.StringProperty`, defaults to None. 458 | See :kivy_fchooser:`kivy.uix.filechooser.FileChooserController.rootpath`. 459 | 460 | .. versionadded:: 1.1 461 | ''' 462 | 463 | favorites = ListProperty([]) 464 | '''A list of the paths added to the favorites link bar. Each element 465 | is a tuple where the first element is a string containing the full path 466 | to the location, while the second element is a string with the name of 467 | path to be displayed. 468 | 469 | :data:`favorites` is an :class:`~kivy.properties.ListProperty`, 470 | defaults to '[]'. 471 | ''' 472 | 473 | def on_success(self): 474 | pass 475 | 476 | def on_canceled(self): 477 | pass 478 | 479 | def on_submit(self): 480 | pass 481 | 482 | def __init__(self, **kwargs): 483 | super(FileBrowser, self).__init__(**kwargs) 484 | Clock.schedule_once(self._post_init) 485 | 486 | def _post_init(self, *largs): 487 | self.ids.icon_view.bind( 488 | selection=partial(self._attr_callback, 'selection'), 489 | path=partial(self._attr_callback, 'path'), 490 | filters=partial(self._attr_callback, 'filters'), 491 | filter_dirs=partial(self._attr_callback, 'filter_dirs'), 492 | show_hidden=partial(self._attr_callback, 'show_hidden'), 493 | multiselect=partial(self._attr_callback, 'multiselect'), 494 | dirselect=partial(self._attr_callback, 'dirselect'), 495 | rootpath=partial(self._attr_callback, 'rootpath')) 496 | self.ids.list_view.bind( 497 | selection=partial(self._attr_callback, 'selection'), 498 | path=partial(self._attr_callback, 'path'), 499 | filters=partial(self._attr_callback, 'filters'), 500 | filter_dirs=partial(self._attr_callback, 'filter_dirs'), 501 | show_hidden=partial(self._attr_callback, 'show_hidden'), 502 | multiselect=partial(self._attr_callback, 'multiselect'), 503 | dirselect=partial(self._attr_callback, 'dirselect'), 504 | rootpath=partial(self._attr_callback, 'rootpath')) 505 | 506 | def _shorten_filenames(self, filenames): 507 | if not len(filenames): 508 | return '' 509 | elif len(filenames) == 1: 510 | return filenames[0] 511 | elif len(filenames) == 2: 512 | return filenames[0] + ', ' + filenames[1] 513 | else: 514 | return filenames[0] + ', _..._, ' + filenames[-1] 515 | 516 | def _attr_callback(self, attr, obj, value): 517 | setattr(self, attr, getattr(obj, attr)) 518 | 519 | 520 | if __name__ == '__main__': 521 | import os 522 | from kivy.app import App 523 | 524 | class TestApp(App): 525 | 526 | def build(self): 527 | user_path = os.path.join(get_home_directory(), 'Documents') 528 | browser = FileBrowser(select_string='Select', 529 | favorites=[(user_path, 'Documents')]) 530 | browser.bind(on_success=self._fbrowser_success, 531 | on_canceled=self._fbrowser_canceled, 532 | on_submit=self._fbrowser_submit) 533 | return browser 534 | 535 | def _fbrowser_canceled(self, instance): 536 | print('cancelled, Close self.') 537 | 538 | def _fbrowser_success(self, instance): 539 | print(instance.selection) 540 | 541 | def _fbrowser_submit(self, instance): 542 | print(instance.selection) 543 | 544 | TestApp().run() 545 | -------------------------------------------------------------------------------- /kivy_garden/filebrowser/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.1.3.dev0' 2 | -------------------------------------------------------------------------------- /kivy_garden/filebrowser/tests/test_import.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_flower(): 5 | from kivy_garden.filebrowser import FileBrowser 6 | widget = FileBrowser() 7 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivy-garden/filebrowser/6aec4058585bdacc5364f4ee267e694bcfde8589/screenshot.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """See README.md for package documentation.""" 2 | 3 | from setuptools import setup, find_namespace_packages 4 | 5 | from io import open 6 | from os import path 7 | 8 | here = path.abspath(path.dirname(__file__)) 9 | 10 | filename = path.join(here, 'kivy_garden', 'filebrowser', '_version.py') 11 | locals = {} 12 | with open(filename, "rb") as fh: 13 | exec(compile(fh.read(), filename, 'exec'), globals(), locals) 14 | __version__ = locals['__version__'] 15 | 16 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 17 | long_description = f.read() 18 | 19 | URL = 'https://github.com/kivy-garden/filebrowser' 20 | 21 | setup( 22 | name='kivy_garden.filebrowser', 23 | version=__version__, 24 | description='An advanced kivy file browser.', 25 | long_description=long_description, 26 | long_description_content_type='text/markdown', 27 | url=URL, 28 | author='Kivy', 29 | author_email='kivy@kivy.org', 30 | classifiers=[ 31 | 'Development Status :: 4 - Beta', 32 | 'Intended Audience :: Developers', 33 | 'Topic :: Software Development :: Libraries', 34 | 'License :: OSI Approved :: MIT License', 35 | 'Programming Language :: Python :: 3.5', 36 | 'Programming Language :: Python :: 3.6', 37 | 'Programming Language :: Python :: 3.7', 38 | 'Programming Language :: Python :: 3.8', 39 | ], 40 | keywords='Kivy kivy-garden', 41 | 42 | packages=find_namespace_packages(include=['kivy_garden.*']), 43 | install_requires=[], 44 | extras_require={ 45 | 'dev': ['pytest>=3.6', 'pytest-cov', 'pytest-asyncio', 46 | 'sphinx_rtd_theme'], 47 | 'ci': ['coveralls', 'pycodestyle'], 48 | }, 49 | package_data={}, 50 | data_files=[], 51 | entry_points={}, 52 | project_urls={ 53 | 'Bug Reports': URL + '/issues', 54 | 'Source': URL, 55 | }, 56 | ) 57 | -------------------------------------------------------------------------------- /tools/hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | # To enable this hook, rename this file to "pre-commit". 9 | 10 | if git rev-parse --verify HEAD >/dev/null 2>&1 11 | then 12 | against=HEAD 13 | else 14 | # Initial commit: diff against an empty tree object 15 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 16 | fi 17 | 18 | # If you want to allow non-ASCII filenames set this variable to true. 19 | allownonascii=$(git config --bool hooks.allownonascii) 20 | 21 | # Redirect output to stderr. 22 | exec 1>&2 23 | 24 | # Cross platform projects tend to avoid non-ASCII filenames; prevent 25 | # them from being added to the repository. We exploit the fact that the 26 | # printable range starts at the space character and ends with tilde. 27 | if [ "$allownonascii" != "true" ] && 28 | # Note that the use of brackets around a tr range is ok here, (it's 29 | # even required, for portability to Solaris 10's /usr/bin/tr), since 30 | # the square bracket bytes happen to fall in the designated range. 31 | test $(git diff --cached --name-only --diff-filter=A -z $against | 32 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 33 | then 34 | cat <<\EOF 35 | Error: Attempt to add a non-ASCII file name. 36 | 37 | This can cause problems if you want to work with people on other platforms. 38 | 39 | To be portable it is advisable to rename the file. 40 | 41 | If you know what you are doing you can disable this check using: 42 | 43 | git config hooks.allownonascii true 44 | EOF 45 | exit 1 46 | fi 47 | 48 | # If there are whitespace errors, print the offending file names and fail. 49 | # exec git diff-index --check --cached $against -- 50 | 51 | git stash save --keep-index --quiet 52 | pytest && pycodestyle && pydocstyle 53 | err=$? 54 | git stash pop --quiet 55 | exit $err 56 | --------------------------------------------------------------------------------