├── .github └── workflows │ └── workflow.yml ├── .gitignore ├── LICENSE ├── README.md ├── doc ├── ADOPTIONS.md ├── CHANGES.md ├── CREDITS ├── HISTORY ├── RELEASING ├── evolution-notes.txt ├── unified_diff_svn.png └── unified_diff_svn.svg ├── example └── example.py ├── patch_ng.py ├── pyproject.toml ├── setup.py └── tests ├── 01uni_multi ├── 01uni_multi.patch ├── [result] │ ├── conf.cpp │ ├── conf.h │ ├── manifest.xml │ ├── updatedlg.cpp │ └── updatedlg.h ├── conf.cpp ├── conf.h ├── manifest.xml ├── updatedlg.cpp └── updatedlg.h ├── 02uni_newline.from ├── 02uni_newline.patch ├── 02uni_newline.to ├── 03trail_fname.from ├── 03trail_fname.patch ├── 03trail_fname.to ├── 04can_patch.from ├── 04can_patch.patch ├── 04can_patch.to ├── 05hg_change.from ├── 05hg_change.patch ├── 05hg_change.to ├── 06nested ├── .hgignore ├── 06nested.patch ├── [result] │ ├── .hgignore │ ├── examples │ │ └── font_comparison.py │ ├── experimental │ │ └── console.py │ ├── pyglet │ │ └── font │ │ │ └── win32.py │ └── tests │ │ ├── app │ │ └── EVENT_LOOP.py │ │ └── font │ │ └── SYSTEM.py ├── examples │ └── font_comparison.py ├── experimental │ └── console.py ├── pyglet │ └── font │ │ └── win32.py └── tests │ ├── app │ └── EVENT_LOOP.py │ └── font │ └── SYSTEM.py ├── 07google_code_wiki.from ├── 07google_code_wiki.patch ├── 07google_code_wiki.to ├── 08create ├── 08create.patch └── [result] │ └── created ├── 09delete ├── 09delete.patch ├── [result] │ └── .gitkeep └── deleted ├── 10fuzzy ├── 10fuzzy.patch ├── Jamroot └── [result] │ └── Jamroot ├── 10fuzzyafter ├── 10fuzzyafter.patch ├── Jamroot └── [result] │ └── Jamroot ├── 10fuzzybefore ├── 10fuzzybefore.patch ├── Jamroot └── [result] │ └── Jamroot ├── 11permission ├── 11permission.patch ├── [result] │ └── some_file └── some_file ├── Descript.ion ├── data ├── autofix │ ├── absolute-path.diff │ ├── parent-path.diff │ └── stripped-trailing-whitespace.diff ├── exotic │ ├── diff.py-python25.diff │ └── dpatch.diff ├── extended │ ├── git-added-new-empty-file.diff │ └── svn-added-new-empty-file.diff ├── failing │ ├── context-format.diff │ ├── missing-hunk-line.diff │ ├── non-empty-patch-for-empty-file.diff │ ├── not-a-patch.log │ └── upload.py ├── git-changed-2-files.diff ├── git-changed-file.diff ├── git-dash-in-filename.diff ├── hg-added-file.diff ├── hg-changed-2-files.diff ├── hg-exported.diff ├── svn-added-new-file-withcontent.diff ├── svn-changed-2-files.diff └── svn-modified-empty-file.diff ├── recoverage.bat └── run_tests.py /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Main workflow 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'doc/**' 7 | - '**/*.md' 8 | - 'LICENSE' 9 | - 'example/**' 10 | - '.gitignore' 11 | workflow_dispatch: 12 | pull_request: 13 | paths-ignore: 14 | - 'doc/**' 15 | - '**/*.md' 16 | - 'LICENSE' 17 | - 'example/**' 18 | - '.gitignore' 19 | 20 | 21 | jobs: 22 | linux-validate: 23 | name: Validate on Linux - Python ${{ matrix.python }} 24 | runs-on: ubuntu-20.04 25 | strategy: 26 | matrix: 27 | python: [ '3.6', '3.8', '3.12' ] 28 | steps: 29 | - uses: actions/checkout@v4 30 | 31 | - name: Setup python 32 | uses: actions/setup-python@v5 33 | with: 34 | python-version: ${{ matrix.python }} 35 | architecture: x64 36 | 37 | - name: Run tests 38 | run: python tests/run_tests.py 39 | 40 | windows-validate: 41 | name: Validate on Windows - Python ${{ matrix.python }} 42 | runs-on: windows-latest 43 | strategy: 44 | matrix: 45 | python: [ '3.6', '3.8', '3.12' ] 46 | steps: 47 | - uses: actions/checkout@v4 48 | 49 | - name: Setup python 50 | uses: actions/setup-python@v5 51 | with: 52 | python-version: ${{ matrix.python }} 53 | architecture: x64 54 | 55 | - name: Run tests 56 | run: python tests/run_tests.py 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build 3 | dist 4 | .idea 5 | patch_ng.egg-info 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | ----------- 3 | 4 | Copyright (c) 2008-2016 anatoly techtonik 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | ---- 25 | 26 | The MIT License (MIT) 27 | 28 | Copyright (c) 2019 JFrog LTD 29 | 30 | 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy 33 | of this software and associated documentation files (the "Software"), to deal 34 | in the Software without restriction, including without limitation the rights 35 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 36 | copies of the Software, and to permit persons to whom the Software is 37 | furnished to do so, subject to the following conditions: 38 | 39 | 40 | 41 | The above copyright notice and this permission notice shall be included in 42 | all copies or substantial portions of the Software. 43 | 44 | 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 47 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 48 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 49 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 50 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 51 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 52 | THE SOFTWARE. 53 | 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI Status](https://github.com/conan-io/python-patch-ng/actions/workflows/workflow.yml/badge.svg)](https://github.com/conan-io/python-patch-ng/actions/workflows/workflow.yml) 2 | [![PyPI](https://img.shields.io/pypi/v/patch-ng)](https://pypi.python.org/pypi/patch-ng) 3 | 4 | ## Patch NG (New Generation) 5 | 6 | #### Library to parse and apply unified diffs. 7 | 8 | #### Why did we fork this project? 9 | 10 | This project is a fork from the original [python-patch](https://github.com/techtonik/python-patch) project. 11 | 12 | As any other project, bugs are common during the development process, the combination of issues + pull requests are 13 | able to keep the constant improvement of a project. However, both community and author need to be aligned. When users, 14 | developers, the community, needs a fix which are important for their projects, but there is no answer from the author, 15 | or the time for response is not enough, then the most plausible way is forking and continuing a parallel development. 16 | 17 | That's way we forked the original and accepted most of PRs waiting for review since jun/2019 (5 months from now). 18 | 19 | ### Features 20 | 21 | * Python >=3.6 compatible 22 | * Automatic correction of 23 | * Linefeeds according to patched file 24 | * Diffs broken by stripping trailing whitespace 25 | * a/ and b/ prefixes 26 | * Single file, which is a command line tool and a library 27 | * No dependencies outside Python stdlib 28 | * Patch format detection (SVN, HG, GIT) 29 | * Nice diffstat histogram 30 | * Linux / Windows / OS X 31 | * Test coverage 32 | 33 | Things that don't work out of the box: 34 | 35 | * File renaming, creation and removal 36 | * Directory tree operations 37 | * Version control specific properties 38 | * Non-unified diff formats 39 | 40 | 41 | ### Usage 42 | 43 | Download **patch_ng.py** and run it with Python. It is a self-contained 44 | module without external dependencies. 45 | 46 | patch_ng.py diff.patch 47 | 48 | You can also run the .zip file. 49 | 50 | python patch-ng-1.17.zip diff.patch 51 | 52 | ### Installation 53 | 54 | **patch_ng.py** is self sufficient. You can copy it into your repository 55 | and use it from here. This setup will always be repeatable. But if 56 | you need to add `patch` module as a dependency, make sure to use strict 57 | specifiers to avoid hitting an API break when version 2 is released: 58 | 59 | pip install "patch-ng" 60 | 61 | 62 | ### Other stuff 63 | 64 | * [CHANGES](doc/CHANGES.md) 65 | * [LICENSE: MIT](LICENSE) 66 | * [CREDITS](doc/CREDITS) 67 | -------------------------------------------------------------------------------- /doc/ADOPTIONS.md: -------------------------------------------------------------------------------- 1 | | Project | Description | patch.py version | Reviewed | 2 | |:--------|:------------|:-----------------|:---------| 3 | | [conda-recipes](https://github.com/conda/conda-recipes/tree/master/python-patch)| conda package | [1.12.11](https://github.com/conda/conda-recipes/blob/master/python-patch/patch.py) | 2016-01-17 | 4 | | [collective.recipe.patch](https://pypi.python.org/pypi/collective.recipe.patch/0.2.2) | buildout recipe for patching eggs | [8.06-1+](https://github.com/garbas/collective.recipe.patch/blob/master/collective/recipe/patch/patch.py) | 2014-01-17 | 5 | | [Linux Kernel Backports](https://backports.wiki.kernel.org/index.php/Documentation) | backporting Linux upstream device drivers for usage on older kernels | [1.12.12dev+](https://git.kernel.org/cgit/linux/kernel/git/backports/backports.git/tree/lib/patch.py) | 2014-01-17 | 6 | | [LuaPatch](http://lua-users.org/wiki/LuaPatch) | rewrite of patch.py for Lua by David Manura | 8.06-1| 2014-01-17 | 7 | | [OpenHatch](https://openhatch.org/) | help wannabe open source developers find interesting projects | [10.04-2+](https://github.com/openhatch/oh-mainline/blob/master/vendor/packages/python-patch/patch.py) | 2014-01-17 | 8 | | [nose](https://nose.readthedocs.org/en/latest/) | `nose` extends unittest to make testing easier | [10.04-2+](https://github.com/nose-devs/nose/blob/master/patch.py) | 2014-01-17 | 9 | | [pypatch](https://pypi.python.org/pypi/pypatch/0.5.1) | automatically patch installed python modules | 1.12.11 | 2014-01-17 | 10 | -------------------------------------------------------------------------------- /doc/CHANGES.md: -------------------------------------------------------------------------------- 1 | ##### 1.16 2 | 3 | - Python 3 support, thanks to Yen Chi Hsuan (@yan12125) 4 | (pull request #36) 5 | 6 | ##### 1.15 7 | 8 | - Project moved to GitHub 9 | - patch-1.15.zip archive is now executable 10 | - improved Git patch detection thanks to @mspncp (#32) 11 | - tests/data contains database of all possible patches 12 | - tests suite scan now processes tests/data automatically 13 | - API changes: 14 | + setdebug() initializes logging and enables debug info 15 | 16 | ##### 1.14.2 17 | 18 | - --revert option to apply patches in reverse order (unpatch) 19 | - support for broken patches generated by online Google Code editor 20 | - API changes: 21 | + PatchSet and Patch objects are now iterable 22 | + new PatchSet.findfile() contains logic detecting filename to patch 23 | + PatchSet.revert() 24 | - make directory based tests easier to create and run manually 25 | - fix xnormpath with Windows paths on Linux 26 | (issue #24, found by Philippe Ombredanne) 27 | 28 | ##### 1.13 29 | 30 | - diffstat output now also shows size delta in bytes 31 | - added --directory (-d) option to specify root when applying patches 32 | - hunk headers produced by `diff -p` option are now parsed and accessible 33 | (issue #22, found by Philippe Ombredanne) 34 | - API changes: 35 | + Hunk.desc field to access hunk headers content 36 | + PatchSet.apply() gets `root` keyword argument for the working dir 37 | when applying patches (issue #7) 38 | - improve error message for missing files 39 | - improve docs (fix issue #5) 40 | 41 | ##### 1.12.11 Major API Break 42 | 43 | - patch.py can read patches from stdin 44 | - patch.py can show nice histogram with --diffstat option 45 | - added detection of SVN, GIT and HG patch types, unrecognized 46 | patches marked as PLAIN 47 | - added error reporting for parsing functions and helpers (now they 48 | return False if parsing failed) - make sure you handle this correctly 49 | - added normalization to filenames to protect against patching files 50 | using absolute paths or files in parent directories 51 | - test run patch.py on all patches submitted to Python bug tracker, which 52 | resulted in improved parsing and error handling for some corner cases 53 | - improved logging 54 | - API changes 55 | * fromfile(), fromstring() and fromurl() now return False on errors 56 | * previous Patch is renamed to PatchSet, new Patch is single file entry 57 | * Patch.header is now a list of strings 58 | * PatchSet.parse() now returns True if parsing completed without errors 59 | + PatchSet.__len__() 60 | + PatchSet.diffstat() 61 | + PatchSet.type and Patch.type 62 | + PatchSet.errors and 63 | + xisabs() cross-platform version of `os.path.isabs()` 64 | + xnormpath() forward slashed version of `os.path.normpath()` 65 | + xstrip() to strip absolute path prefixes 66 | 67 | ##### 11.01 68 | 69 | - patch.py can read patches from web 70 | - patch.py returns -1 if there were errors during patching 71 | - store patch headers (necessary for future DIFF/SVN/HG/GIT detection) 72 | - report about extra bytes at the end after patch is parsed 73 | - API changes 74 | + fromurl() 75 | * Patch.apply() now returns True on success 76 | 77 | ##### 10.11 78 | 79 | - fixed fromstring() failure due to invalid StringIO import (issue #9) 80 | (thanks john.stumpo for reporting) 81 | - added --verbose and --quiet options 82 | - improved message logging 83 | - change "successfully patched..." message to INFO instead of WARN 84 | (thanks Alex Stewart for reporting and patch) 85 | - skip __main__ imports when used as a library (patch by Alex Stewart) 86 | - API changes 87 | * renamed class HunkInfo to Hunk 88 | + Patch.type placeholder (no detection yet - parser is not ready) 89 | + constants for patch types DIFF/PLAIN, HG/MERCURIAL, SVN/SUBVERSION 90 | + Patch.header for saving headers which can be used later to extract 91 | additional meta information such as commit message 92 | - internal: improving parser speed by allowing blocks fetch lines on 93 | demand 94 | - test suite improvements 95 | 96 | ##### 10.04 97 | 98 | - renamed debug option to --debug 99 | - API changes 100 | * method names are now underscored for consistency with difflib 101 | + addded Patch.can_patch(filename) to test if source file is in list 102 | of source filenames and can be patched 103 | * use designated logger "python_patch" instead of default 104 | 105 | ##### 9.08-2 106 | 107 | - compatibility fix for Python 2.4 108 | 109 | ##### 9.08-1 110 | 111 | - fixed issue #2 - remove trailing whitespaces from filename 112 | (thanks James from Twisted Fish) 113 | - API changes 114 | + added Patch and HunkInfo classes 115 | * moved utility methods into Patch 116 | + build Patch object by specifying stream to constructor 117 | or use top level functions fromfile() and fromstring() 118 | - added test suite 119 | 120 | ##### 8.06-2 121 | 122 | - compatibility fix for Python 2.4 123 | 124 | ##### 8.06-1 125 | 126 | - initial release 127 | -------------------------------------------------------------------------------- /doc/CREDITS: -------------------------------------------------------------------------------- 1 | I'd like to thank the following people who contributed to 2 | development of this library: 3 | 4 | 5 | Alex Stewart 6 | Wladimir J. van der Laan (laanwj) 7 | azasypkin 8 | Philippe Ombredanne 9 | mspncp 10 | Yen Chi Hsuan (@yan12125) 11 | -------------------------------------------------------------------------------- /doc/HISTORY: -------------------------------------------------------------------------------- 1 | In 2008 there was no reliable Windows tool to apply patches, 2 | and there was no cross-platform solution that could be safely 3 | run by web server process. 4 | 5 | (UNIX *patch* utility)[http://www.gnu.org/software/patch/] was 6 | (ported to windows)[http://gnuwin32.sourceforge.net/packages/patch.htm], 7 | but there were (a couple of bugs) 8 | [http://www.google.com/search?q=Assertion+failed%3A+hunk%2C+file+patch.c] 9 | that proved that it can not be run securely in web server process. 10 | The utility was also hard to tweak without a C compiler, it messed 11 | badly with LF and CRLF line end differences, and so this project 12 | was born. 13 | 14 | *patch.py* was meant to be a cross-platoform tool with intuitive 15 | defaults, taking care of the most problems (e.g. line end 16 | differences) automatically. 17 | -------------------------------------------------------------------------------- /doc/RELEASING: -------------------------------------------------------------------------------- 1 | * [ ] Pack .zip archive 2 | 3 | pip install pypack 4 | python -m pypack patch.py 5 | 6 | * [ ] Write changelog 7 | 8 | * [ ] Upload archive to PyPI (manually for now) 9 | * [ ] Create new version https://pypi.python.org/pypi?%3Aaction=submit_form&name=patch 10 | * [ ] Upload .zip for this version 11 | 12 | * [ ] Update PyPI description 13 | * [ ] Download PKG-INFO 14 | * [ ] Edit and upload 15 | 16 | * [ ] Tag release 17 | 18 | git tag -a 19 | git push --follow-tags 20 | -------------------------------------------------------------------------------- /doc/evolution-notes.txt: -------------------------------------------------------------------------------- 1 | patchset evolution 2 | 3 | (diff era) 4 | 1. change some content in a stream 5 | 1. change some lines in a file 6 | 1. protect change with context 7 | 1. change several files 8 | 9 | (git diff era) 10 | 2. create file 11 | 2. rename file 12 | 2. move file 13 | 2. copy file 14 | 2. copy and rename 15 | 2. move and rename 16 | 2. remove file 17 | 18 | 3. know file attributes 19 | 3. know file mime-type 20 | 3. know file binary/text 21 | 3. change file attributes 22 | 23 | (2D patch jump) 24 | 4. create directory 25 | 4. rename directory 26 | 4. move directory 27 | 4. copy directory 28 | 4. copy and rename 29 | 4. move and rename 30 | 31 | 5. know directory contents 32 | 5. record directory tree in 1D structure 33 | 5. record changes for 2D structure in 1D structure 34 | 35 | -------------------------------------------------------------------------------- /doc/unified_diff_svn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conan-io/python-patch-ng/885476a6e07048159654b8559cfbb2a6c7e28d96/doc/unified_diff_svn.png -------------------------------------------------------------------------------- /example/example.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from patch import fromfile, fromstring 4 | 5 | class PatchLogHandler(logging.Handler): 6 | def __init__(self): 7 | logging.Handler.__init__(self, logging.DEBUG) 8 | 9 | def emit(self, record): 10 | logstr = self.format(record) 11 | print logstr 12 | 13 | patchlog = logging.getLogger("patch") 14 | patchlog.handlers = [] 15 | patchlog.addHandler(PatchLogHandler()) 16 | 17 | patch = fromstring("""--- /dev/null 18 | +++ b/newfile 19 | @@ -0,0 +0,3 @@ 20 | +New file1 21 | +New file2 22 | +New file3 23 | """) 24 | 25 | patch.apply(root=os.getcwd(), strip=0) 26 | 27 | 28 | with open("newfile", "rb") as f: 29 | newfile = f.read() 30 | assert "New file1\nNew file2\nNew file3\n" == newfile 31 | 32 | patch = fromstring("""--- a/newfile 33 | +++ /dev/null 34 | @@ -0,3 +0,0 @@ 35 | -New file1 36 | -New file2 37 | -New file3 38 | """) 39 | 40 | result = patch.apply(root=os.getcwd(), strip=0) 41 | 42 | assert os.path.exists("newfile") is False -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """A setuptools based setup module. 2 | See: 3 | https://packaging.python.org/en/latest/distributing.html 4 | https://github.com/pypa/sampleproject 5 | """ 6 | 7 | # Always prefer setuptools over distutils 8 | import re 9 | import os 10 | from setuptools import setup 11 | # To use a consistent encoding 12 | from codecs import open 13 | 14 | 15 | here = os.path.abspath(os.path.dirname(__file__)) 16 | 17 | # Get the long description from the README file 18 | with open(os.path.join(here, 'README.md'), encoding='utf-8') as f: 19 | long_description = f.read() 20 | 21 | 22 | def get_requires(filename): 23 | requirements = [] 24 | with open(filename) as req_file: 25 | for line in req_file.read().splitlines(): 26 | if not line.strip().startswith("#"): 27 | requirements.append(line) 28 | return requirements 29 | 30 | 31 | def load_version(): 32 | """Loads a file content""" 33 | filename = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), 34 | "patch_ng.py")) 35 | with open(filename, "rt") as version_file: 36 | content = version_file.read() 37 | version = re.search('__version__ = "([0-9a-z.-]+)"', content).group(1) 38 | return version 39 | 40 | setup( 41 | name='patch-ng', 42 | python_requires='>=3.6', 43 | # Versions should comply with PEP440. For a discussion on single-sourcing 44 | # the version across setup.py and the project code, see 45 | # https://packaging.python.org/en/latest/single_source_version.html 46 | version=load_version(), 47 | 48 | # This is an optional longer description of your project that represents 49 | # the body of text which users will see when they visit PyPI. 50 | # 51 | # Often, this is the same as your README, so you can just read it in from 52 | # that file directly (as we have already done above) 53 | # 54 | # This field corresponds to the "Description" metadata field: 55 | # https://packaging.python.org/specifications/core-metadata/#description-optional 56 | long_description=long_description, # Optional 57 | 58 | description='Library to parse and apply unified diffs.', 59 | 60 | # The project's main homepage. 61 | url='https://github.com/conan-io/python-patch', 62 | 63 | # Author details 64 | author='Conan.io', 65 | author_email='info@conan.io', 66 | 67 | # Choose your license 68 | license='MIT', 69 | 70 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 71 | classifiers=[ 72 | 'Development Status :: 5 - Production/Stable', 73 | 'Intended Audience :: Developers', 74 | 'Topic :: Software Development :: Build Tools', 75 | 'License :: OSI Approved :: MIT License', 76 | 'Programming Language :: Python :: 3', 77 | 'Programming Language :: Python :: 3.6', 78 | 'Programming Language :: Python :: 3.7', 79 | 'Programming Language :: Python :: 3.8' 80 | ], 81 | 82 | # What does your project relate to? 83 | keywords=['patch', 'parse', 'diff', 'strip', 'diffstat'], 84 | 85 | # You can just specify the packages manually here if your project is 86 | # simple. Or you can use find_packages(). 87 | # packages=find_packages(exclude=['tests']), 88 | 89 | # Alternatively, if you want to distribute just a my_module.py, uncomment 90 | # this: 91 | py_modules=["patch_ng"], 92 | 93 | # List run-time dependencies here. These will be installed by pip when 94 | # your project is installed. For an analysis of "install_requires" vs pip's 95 | # requirements files see: 96 | # https://packaging.python.org/en/latest/requirements.html 97 | # install_requires=get_requires('requirements.txt'), 98 | 99 | # List additional groups of dependencies here (e.g. development 100 | # dependencies). You can install these using the following syntax, 101 | # for example: 102 | # $ pip install -e .[dev,test] 103 | #extras_require={ 104 | # 'test': get_requires(os.path.join('tests', 'requirements_test.txt')) 105 | #}, 106 | 107 | # If there are data files included in your packages that need to be 108 | # installed, specify them here. If using Python 2.6 or less, then these 109 | # have to be included in MANIFEST.in as well. 110 | package_data={ 111 | '': ['*.md', 'LICENSE'], 112 | }, 113 | 114 | # Although 'package_data' is the preferred approach, in some case you may 115 | # need to place data files outside of your packages. See: 116 | # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa 117 | # In this case, 'data_file' will be installed into '/my_data' 118 | # data_files=[('my_data', ['data/data_file'])], 119 | 120 | # To provide executable scripts, use entry points in preference to the 121 | # "scripts" keyword. Entry points provide cross-platform support and allow 122 | # pip to create the appropriate form of executable for the target platform. 123 | #entry_points={ 124 | # 'console_scripts': [ 125 | # 'patch_ng.py=patch', 126 | # ], 127 | #}, 128 | ) 129 | -------------------------------------------------------------------------------- /tests/01uni_multi/01uni_multi.patch: -------------------------------------------------------------------------------- 1 | Index: updatedlg.cpp 2 | =================================================================== 3 | --- updatedlg.cpp (revision 5095) 4 | +++ updatedlg.cpp (working copy) 5 | @@ -94,11 +94,13 @@ 6 | lst->InsertColumn(1, _("Version")); 7 | lst->InsertColumn(2, _("Installed")); 8 | lst->InsertColumn(3, _("Size"), wxLIST_FORMAT_RIGHT); 9 | + lst->InsertColumn(4, _("Rev")); 10 | 11 | - lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3) - 2); // 1st column takes all remaining space 12 | + lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3 + 40) - 6 ); // 1st column takes all remaining space 13 | lst->SetColumnWidth(1, 64); 14 | lst->SetColumnWidth(2, 64); 15 | lst->SetColumnWidth(3, 64); 16 | + lst->SetColumnWidth(4, 40); 17 | } 18 | 19 | void UpdateDlg::AddRecordToList(UpdateRec* rec) 20 | @@ -111,8 +113,20 @@ 21 | lst->SetItem(idx, 1, rec->version); 22 | lst->SetItem(idx, 2, rec->installed_version); 23 | lst->SetItem(idx, 3, rec->size); 24 | + lst->SetItem(idx, 4, rec->revision); 25 | } 26 | 27 | +wxString UpdateDlg::GetListColumnText(int idx, int col) { 28 | + wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl); 29 | + int index = idx == -1 ? lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) : idx; 30 | + wxListItem info; 31 | + info.SetId(index); 32 | + info.SetColumn(col); 33 | + info.SetMask(wxLIST_MASK_TEXT); 34 | + lst->GetItem(info); 35 | + return info.GetText(); 36 | +} 37 | + 38 | void UpdateDlg::SetListColumnText(int idx, int col, const wxString& text) 39 | { 40 | wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl); 41 | @@ -393,7 +407,9 @@ 42 | if (index == -1) 43 | return 0; 44 | wxString title = lst->GetItemText(index); 45 | - return FindRecByTitle(title, m_Recs, m_RecsCount); 46 | + wxString version = GetListColumnText(index, 1); 47 | + wxString revision = GetListColumnText(index, 4); 48 | + return FindRec(title, version, revision, m_Recs, m_RecsCount); 49 | } 50 | 51 | void UpdateDlg::DownloadFile(bool dontInstall) 52 | Index: updatedlg.h 53 | =================================================================== 54 | --- updatedlg.h (revision 5095) 55 | +++ updatedlg.h (working copy) 56 | @@ -49,6 +49,7 @@ 57 | UpdateRec* GetRecFromListView(); 58 | void CreateListColumns(); 59 | void AddRecordToList(UpdateRec* rec); 60 | + wxString GetListColumnText(int idx, int col); 61 | void SetListColumnText(int idx, int col, const wxString& text); 62 | 63 | wxString GetConfFilename(); 64 | Index: manifest.xml 65 | =================================================================== 66 | --- manifest.xml (revision 5095) 67 | +++ manifest.xml (working copy) 68 | @@ -2,18 +2,19 @@ 69 | 70 | 71 | 72 | - 73 | - 74 | + 75 | + 76 | 77 | 78 | 79 | 80 | 86 | + Julian R Seward for libbzip2. 87 | + 88 | + libbzip2 copyright notice: 89 | + bzip2 and associated library libbzip2, are 90 | + copyright (C) 1996-2000 Julian R Seward. 91 | + All rights reserved." /> 92 | 93 | 94 | 95 | Index: conf.cpp 96 | =================================================================== 97 | --- conf.cpp (revision 5095) 98 | +++ conf.cpp (working copy) 99 | @@ -46,10 +46,16 @@ 100 | // fix title 101 | // devpaks.org has changed the title to contain some extra info 102 | // e.g.: [libunicows Library version: 1.1.1 Devpak revision: 1sid] 103 | - // we don't need this extra info, so if we find it we remove it 104 | - int pos = rec.title.Find(_T("Library version:")); 105 | + int pos = rec.title.Lower().Find(_T("library version:")); 106 | if (pos != -1) 107 | { 108 | + int revpos = rec.title.Lower().Find(_T("devpak revision:")); 109 | + if (revpos != -1) { 110 | + rec.revision = rec.title.Mid(revpos).AfterFirst(_T(':')).Trim(false); 111 | + rec.revision.Replace(_T("\t"), _T(" ")); 112 | + rec.revision = rec.revision.BeforeFirst(_T(' ')); 113 | + } 114 | + 115 | rec.title.Truncate(pos); 116 | rec.title = rec.title.Trim(false); 117 | rec.title = rec.title.Trim(true); 118 | @@ -60,7 +66,7 @@ 119 | rec.remote_file = ini.GetKeyValue(i, _T("RemoteFilename")); 120 | rec.local_file = ini.GetKeyValue(i, _T("LocalFilename")); 121 | rec.groups = GetArrayFromString(ini.GetKeyValue(i, _T("Group")), _T(",")); 122 | - rec.install = ini.GetKeyValue(i, _T("InstallPath")); 123 | + rec.install_path = ini.GetKeyValue(i, _T("InstallPath")); 124 | rec.version = ini.GetKeyValue(i, _T("Version")); 125 | ini.GetKeyValue(i, _T("Size")).ToLong(&rec.bytes); 126 | rec.date = ini.GetKeyValue(i, _T("Date")); 127 | @@ -99,12 +105,17 @@ 128 | return list; 129 | } 130 | 131 | -UpdateRec* FindRecByTitle(const wxString& title, UpdateRec* list, int count) 132 | +UpdateRec* FindRec(const wxString& title, const wxString& version, const wxString& revision, UpdateRec* list, int count) 133 | { 134 | for (int i = 0; i < count; ++i) 135 | { 136 | - if (list[i].title == title) 137 | - return &list[i]; 138 | + if (list[i].title == title && list[i].version == version) { 139 | + if (revision.IsEmpty()) { 140 | + return &list[i]; 141 | + } else if (list[i].revision == revision) { 142 | + return &list[i]; 143 | + } 144 | + } 145 | } 146 | return 0; 147 | } 148 | Index: conf.h 149 | =================================================================== 150 | --- conf.h (revision 5095) 151 | +++ conf.h (working copy) 152 | @@ -7,7 +7,7 @@ 153 | 154 | struct UpdateRec 155 | { 156 | - wxString entry; 157 | + wxString entry; //! .entry filename for installed 158 | wxString title; 159 | wxString name; 160 | wxString desc; 161 | @@ -15,8 +15,9 @@ 162 | wxString remote_file; 163 | wxString local_file; 164 | wxArrayString groups; 165 | - wxString install; 166 | + wxString install_path; //! ignored 167 | wxString version; 168 | + wxString revision; 169 | wxString installed_version; 170 | long int bytes; 171 | float kilobytes; 172 | @@ -31,7 +32,7 @@ 173 | extern wxString g_MasterPath; 174 | 175 | UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath); 176 | -UpdateRec* FindRecByTitle(const wxString& title, UpdateRec* list, int count); 177 | +UpdateRec* FindRec(const wxString& title, const wxString& version, const wxString& revision, UpdateRec* list, int count); 178 | // utility 179 | wxString GetSizeString(int bytes); 180 | 181 | -------------------------------------------------------------------------------- /tests/01uni_multi/[result]/conf.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3 3 | * http://www.gnu.org/licenses/gpl-3.0.html 4 | * 5 | * $Revision: 4909 $ 6 | * $Id: conf.cpp 4909 2008-02-27 13:15:26Z mortenmacfly $ 7 | * $HeadURL: http://svn.berlios.de/svnroot/repos/codeblocks/tags/8.02/src/plugins/contrib/devpak_plugin/conf.cpp $ 8 | */ 9 | 10 | #include "conf.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | wxString g_MasterPath; 17 | 18 | wxString GetSizeString(int bytes) 19 | { 20 | wxString ret; 21 | float kilobytes = (float)bytes / 1024.0f; 22 | float megabytes = kilobytes / 1024.0f; 23 | if (megabytes >= 1.0f) 24 | ret.Printf(_("%.2f MB"), megabytes); 25 | else if (kilobytes >= 1.0f) 26 | ret.Printf(_("%.2f KB"), kilobytes); 27 | else 28 | ret.Printf(_("%ld bytes"), bytes); 29 | return ret; 30 | } 31 | 32 | UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath) 33 | { 34 | *recCount = 0; 35 | int groupsCount = ini.GetGroupsCount(); 36 | if (groupsCount == 0) 37 | return 0; 38 | 39 | UpdateRec* list = new UpdateRec[ini.GetGroupsCount()]; 40 | for (int i = 0; i < groupsCount; ++i) 41 | { 42 | UpdateRec& rec = list[i]; 43 | 44 | rec.title = ini.GetGroupName(i); 45 | 46 | // fix title 47 | // devpaks.org has changed the title to contain some extra info 48 | // e.g.: [libunicows Library version: 1.1.1 Devpak revision: 1sid] 49 | int pos = rec.title.Lower().Find(_T("library version:")); 50 | if (pos != -1) 51 | { 52 | int revpos = rec.title.Lower().Find(_T("devpak revision:")); 53 | if (revpos != -1) { 54 | rec.revision = rec.title.Mid(revpos).AfterFirst(_T(':')).Trim(false); 55 | rec.revision.Replace(_T("\t"), _T(" ")); 56 | rec.revision = rec.revision.BeforeFirst(_T(' ')); 57 | } 58 | 59 | rec.title.Truncate(pos); 60 | rec.title = rec.title.Trim(false); 61 | rec.title = rec.title.Trim(true); 62 | } 63 | 64 | rec.name = ini.GetKeyValue(i, _T("Name")); 65 | rec.desc = ini.GetKeyValue(i, _T("Description")); 66 | rec.remote_file = ini.GetKeyValue(i, _T("RemoteFilename")); 67 | rec.local_file = ini.GetKeyValue(i, _T("LocalFilename")); 68 | rec.groups = GetArrayFromString(ini.GetKeyValue(i, _T("Group")), _T(",")); 69 | rec.install_path = ini.GetKeyValue(i, _T("InstallPath")); 70 | rec.version = ini.GetKeyValue(i, _T("Version")); 71 | ini.GetKeyValue(i, _T("Size")).ToLong(&rec.bytes); 72 | rec.date = ini.GetKeyValue(i, _T("Date")); 73 | rec.installable = ini.GetKeyValue(i, _T("Execute")) == _T("1"); 74 | 75 | // read .entry file (if exists) 76 | rec.entry = (!rec.name.IsEmpty() ? rec.name : wxFileName(rec.local_file).GetName()) + _T(".entry"); 77 | IniParser p; 78 | p.ParseFile(appPath + rec.entry); 79 | rec.installed_version = p.GetValue(_T("Setup"), _T("AppVersion")); 80 | 81 | rec.downloaded = wxFileExists(appPath + _T("/") + rec.local_file); 82 | rec.installed = !rec.installed_version.IsEmpty(); 83 | 84 | // calculate size 85 | rec.size = GetSizeString(rec.bytes); 86 | 87 | // fix-up 88 | if (rec.name.IsEmpty()) 89 | rec.name = rec.title; 90 | rec.desc.Replace(_T(""), _T("\n")); 91 | rec.desc.Replace(_T(""), _T("\r")); 92 | wxURL url(rec.remote_file); 93 | if (!url.GetServer().IsEmpty()) 94 | { 95 | rec.remote_server = url.GetScheme() + _T("://") + url.GetServer(); 96 | int pos = rec.remote_file.Find(url.GetServer()); 97 | if (pos != wxNOT_FOUND) 98 | rec.remote_file.Remove(0, pos + url.GetServer().Length() + 1); 99 | } 100 | else 101 | rec.remote_server = currentServer; 102 | } 103 | 104 | *recCount = groupsCount; 105 | return list; 106 | } 107 | 108 | UpdateRec* FindRec(const wxString& title, const wxString& version, const wxString& revision, UpdateRec* list, int count) 109 | { 110 | for (int i = 0; i < count; ++i) 111 | { 112 | if (list[i].title == title && list[i].version == version) { 113 | if (revision.IsEmpty()) { 114 | return &list[i]; 115 | } else if (list[i].revision == revision) { 116 | return &list[i]; 117 | } 118 | } 119 | } 120 | return 0; 121 | } 122 | -------------------------------------------------------------------------------- /tests/01uni_multi/[result]/conf.h: -------------------------------------------------------------------------------- 1 | #ifndef CONF_H 2 | #define CONF_H 3 | 4 | #include 5 | #include 6 | #include "cbiniparser.h" 7 | 8 | struct UpdateRec 9 | { 10 | wxString entry; //! .entry filename for installed 11 | wxString title; 12 | wxString name; 13 | wxString desc; 14 | wxString remote_server; 15 | wxString remote_file; 16 | wxString local_file; 17 | wxArrayString groups; 18 | wxString install_path; //! ignored 19 | wxString version; 20 | wxString revision; 21 | wxString installed_version; 22 | long int bytes; 23 | float kilobytes; 24 | float megabytes; 25 | wxString size; 26 | wxString date; 27 | bool installable; 28 | bool downloaded; 29 | bool installed; 30 | }; 31 | 32 | extern wxString g_MasterPath; 33 | 34 | UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath); 35 | UpdateRec* FindRec(const wxString& title, const wxString& version, const wxString& revision, UpdateRec* list, int count); 36 | // utility 37 | wxString GetSizeString(int bytes); 38 | 39 | #endif // CONF_H 40 | -------------------------------------------------------------------------------- /tests/01uni_multi/[result]/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/01uni_multi/[result]/updatedlg.h: -------------------------------------------------------------------------------- 1 | #ifndef UPDATEDLG_H 2 | #define UPDATEDLG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "cbnetwork.h" 8 | #include "conf.h" 9 | 10 | class UpdateDlg : public wxDialog 11 | { 12 | public: 13 | UpdateDlg(wxWindow* parent); 14 | virtual ~UpdateDlg(); 15 | 16 | void EndModal(int retCode); 17 | protected: 18 | void OnFileSelected(wxListEvent& event); 19 | void OnFileDeSelected(wxListEvent& event); 20 | void OnFileRightClick(wxListEvent& event); 21 | void OnTreeSelChanged(wxTreeEvent& event); 22 | void OnDownload(wxCommandEvent& event); 23 | void OnInstall(wxCommandEvent& event); 24 | void OnUninstall(wxCommandEvent& event); 25 | void OnDownloadAndInstall(wxCommandEvent& event); 26 | void OnUpdate(wxCommandEvent& event); 27 | void OnServerChange(wxCommandEvent& event); 28 | void OnFilterChange(wxCommandEvent& event); 29 | void OnConnect(wxCommandEvent& event); 30 | void OnDisConnect(wxCommandEvent& event); 31 | void OnProgress(wxCommandEvent& event); 32 | void OnAborted(wxCommandEvent& event); 33 | void OnDownloadStarted(wxCommandEvent& event); 34 | void OnDownloadEnded(wxCommandEvent& event); 35 | void OnUpdateUI(wxUpdateUIEvent& event); 36 | private: 37 | void InternetUpdate(bool forceDownload = false); 38 | void DownloadFile(bool dontInstall = false); 39 | void InstallFile(); 40 | void UninstallFile(); 41 | void InstallMirrors(const wxString& file); 42 | void CreateEntryFile(UpdateRec* rec, const wxString& filename, const wxArrayString& files); 43 | void EnableButtons(bool update = true, bool abort = true); 44 | void FillServers(); 45 | void FillGroups(); 46 | void FillFiles(const wxTreeItemId& id); 47 | void FillFileDetails(const wxListItem& id); 48 | void UpdateStatus(const wxString& status, int curProgress = -1, int maxProgress = -1); 49 | UpdateRec* GetRecFromListView(); 50 | void CreateListColumns(); 51 | void AddRecordToList(UpdateRec* rec); 52 | wxString GetListColumnText(int idx, int col); 53 | void SetListColumnText(int idx, int col, const wxString& text); 54 | 55 | wxString GetConfFilename(); 56 | wxString GetMirrorsFilename() const; 57 | wxString GetCurrentServer() const; 58 | wxString GetBasePath() const; 59 | wxString GetPackagePath() const; 60 | bool FilterRec(UpdateRec* rec); 61 | void ApplyFilter(); 62 | 63 | UpdateRec* m_Recs; 64 | wxArrayString m_Servers; 65 | int m_RecsCount; 66 | int m_CurrFileSize; 67 | int m_LastBlockSize; // for bps 68 | bool m_HasUpdated; 69 | bool m_FirstTimeCheck; 70 | cbNetwork m_Net; 71 | DECLARE_EVENT_TABLE(); 72 | }; 73 | 74 | #endif // UPDATEDLG_H 75 | -------------------------------------------------------------------------------- /tests/01uni_multi/conf.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3 3 | * http://www.gnu.org/licenses/gpl-3.0.html 4 | * 5 | * $Revision: 4909 $ 6 | * $Id: conf.cpp 4909 2008-02-27 13:15:26Z mortenmacfly $ 7 | * $HeadURL: http://svn.berlios.de/svnroot/repos/codeblocks/tags/8.02/src/plugins/contrib/devpak_plugin/conf.cpp $ 8 | */ 9 | 10 | #include "conf.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | wxString g_MasterPath; 17 | 18 | wxString GetSizeString(int bytes) 19 | { 20 | wxString ret; 21 | float kilobytes = (float)bytes / 1024.0f; 22 | float megabytes = kilobytes / 1024.0f; 23 | if (megabytes >= 1.0f) 24 | ret.Printf(_("%.2f MB"), megabytes); 25 | else if (kilobytes >= 1.0f) 26 | ret.Printf(_("%.2f KB"), kilobytes); 27 | else 28 | ret.Printf(_("%ld bytes"), bytes); 29 | return ret; 30 | } 31 | 32 | UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath) 33 | { 34 | *recCount = 0; 35 | int groupsCount = ini.GetGroupsCount(); 36 | if (groupsCount == 0) 37 | return 0; 38 | 39 | UpdateRec* list = new UpdateRec[ini.GetGroupsCount()]; 40 | for (int i = 0; i < groupsCount; ++i) 41 | { 42 | UpdateRec& rec = list[i]; 43 | 44 | rec.title = ini.GetGroupName(i); 45 | 46 | // fix title 47 | // devpaks.org has changed the title to contain some extra info 48 | // e.g.: [libunicows Library version: 1.1.1 Devpak revision: 1sid] 49 | // we don't need this extra info, so if we find it we remove it 50 | int pos = rec.title.Find(_T("Library version:")); 51 | if (pos != -1) 52 | { 53 | rec.title.Truncate(pos); 54 | rec.title = rec.title.Trim(false); 55 | rec.title = rec.title.Trim(true); 56 | } 57 | 58 | rec.name = ini.GetKeyValue(i, _T("Name")); 59 | rec.desc = ini.GetKeyValue(i, _T("Description")); 60 | rec.remote_file = ini.GetKeyValue(i, _T("RemoteFilename")); 61 | rec.local_file = ini.GetKeyValue(i, _T("LocalFilename")); 62 | rec.groups = GetArrayFromString(ini.GetKeyValue(i, _T("Group")), _T(",")); 63 | rec.install = ini.GetKeyValue(i, _T("InstallPath")); 64 | rec.version = ini.GetKeyValue(i, _T("Version")); 65 | ini.GetKeyValue(i, _T("Size")).ToLong(&rec.bytes); 66 | rec.date = ini.GetKeyValue(i, _T("Date")); 67 | rec.installable = ini.GetKeyValue(i, _T("Execute")) == _T("1"); 68 | 69 | // read .entry file (if exists) 70 | rec.entry = (!rec.name.IsEmpty() ? rec.name : wxFileName(rec.local_file).GetName()) + _T(".entry"); 71 | IniParser p; 72 | p.ParseFile(appPath + rec.entry); 73 | rec.installed_version = p.GetValue(_T("Setup"), _T("AppVersion")); 74 | 75 | rec.downloaded = wxFileExists(appPath + _T("/") + rec.local_file); 76 | rec.installed = !rec.installed_version.IsEmpty(); 77 | 78 | // calculate size 79 | rec.size = GetSizeString(rec.bytes); 80 | 81 | // fix-up 82 | if (rec.name.IsEmpty()) 83 | rec.name = rec.title; 84 | rec.desc.Replace(_T(""), _T("\n")); 85 | rec.desc.Replace(_T(""), _T("\r")); 86 | wxURL url(rec.remote_file); 87 | if (!url.GetServer().IsEmpty()) 88 | { 89 | rec.remote_server = url.GetScheme() + _T("://") + url.GetServer(); 90 | int pos = rec.remote_file.Find(url.GetServer()); 91 | if (pos != wxNOT_FOUND) 92 | rec.remote_file.Remove(0, pos + url.GetServer().Length() + 1); 93 | } 94 | else 95 | rec.remote_server = currentServer; 96 | } 97 | 98 | *recCount = groupsCount; 99 | return list; 100 | } 101 | 102 | UpdateRec* FindRecByTitle(const wxString& title, UpdateRec* list, int count) 103 | { 104 | for (int i = 0; i < count; ++i) 105 | { 106 | if (list[i].title == title) 107 | return &list[i]; 108 | } 109 | return 0; 110 | } 111 | -------------------------------------------------------------------------------- /tests/01uni_multi/conf.h: -------------------------------------------------------------------------------- 1 | #ifndef CONF_H 2 | #define CONF_H 3 | 4 | #include 5 | #include 6 | #include "cbiniparser.h" 7 | 8 | struct UpdateRec 9 | { 10 | wxString entry; 11 | wxString title; 12 | wxString name; 13 | wxString desc; 14 | wxString remote_server; 15 | wxString remote_file; 16 | wxString local_file; 17 | wxArrayString groups; 18 | wxString install; 19 | wxString version; 20 | wxString installed_version; 21 | long int bytes; 22 | float kilobytes; 23 | float megabytes; 24 | wxString size; 25 | wxString date; 26 | bool installable; 27 | bool downloaded; 28 | bool installed; 29 | }; 30 | 31 | extern wxString g_MasterPath; 32 | 33 | UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath); 34 | UpdateRec* FindRecByTitle(const wxString& title, UpdateRec* list, int count); 35 | // utility 36 | wxString GetSizeString(int bytes); 37 | 38 | #endif // CONF_H 39 | -------------------------------------------------------------------------------- /tests/01uni_multi/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/01uni_multi/updatedlg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3 3 | * http://www.gnu.org/licenses/gpl-3.0.html 4 | * 5 | * $Revision: 4909 $ 6 | * $Id: updatedlg.cpp 4909 2008-02-27 13:15:26Z mortenmacfly $ 7 | * $HeadURL: http://svn.berlios.de/svnroot/repos/codeblocks/tags/8.02/src/plugins/contrib/devpak_plugin/updatedlg.cpp $ 8 | */ 9 | 10 | #include "updatedlg.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "devpakinstaller.h" 22 | #include "crc32.h" 23 | 24 | #include "manager.h" 25 | #include "configmanager.h" 26 | #include "globals.h" 27 | 28 | int idNet = wxNewId(); 29 | int idPopupInstall = wxNewId(); 30 | int idPopupDownload = wxNewId(); 31 | int idPopupDownloadAndInstall = wxNewId(); 32 | int idPopupUninstall = wxNewId(); 33 | 34 | BEGIN_EVENT_TABLE(UpdateDlg, wxDialog) 35 | EVT_UPDATE_UI(-1, UpdateDlg::OnUpdateUI) 36 | EVT_TREE_SEL_CHANGED(XRCID("tvCategories"), UpdateDlg::OnTreeSelChanged) 37 | EVT_LIST_ITEM_SELECTED(XRCID("lvFiles"), UpdateDlg::OnFileSelected) 38 | EVT_LIST_ITEM_DESELECTED(XRCID("lvFiles"), UpdateDlg::OnFileDeSelected) 39 | EVT_LIST_ITEM_RIGHT_CLICK(XRCID("lvFiles"), UpdateDlg::OnFileRightClick) 40 | EVT_MENU(idPopupDownload, UpdateDlg::OnDownload) 41 | EVT_MENU(idPopupDownloadAndInstall, UpdateDlg::OnDownloadAndInstall) 42 | EVT_MENU(idPopupInstall, UpdateDlg::OnInstall) 43 | EVT_MENU(idPopupUninstall, UpdateDlg::OnUninstall) 44 | EVT_COMBOBOX(XRCID("cmbServer"), UpdateDlg::OnServerChange) 45 | EVT_COMBOBOX(XRCID("cmbFilter"), UpdateDlg::OnFilterChange) 46 | EVT_CHECKBOX(XRCID("chkCache"), UpdateDlg::OnServerChange) 47 | EVT_CBNET_CONNECT(idNet, UpdateDlg::OnConnect) 48 | EVT_CBNET_DISCONNECT(idNet, UpdateDlg::OnDisConnect) 49 | EVT_CBNET_PROGRESS(idNet, UpdateDlg::OnProgress) 50 | EVT_CBNET_ABORTED(idNet, UpdateDlg::OnAborted) 51 | EVT_CBNET_START_DOWNLOAD(idNet, UpdateDlg::OnDownloadStarted) 52 | EVT_CBNET_END_DOWNLOAD(idNet, UpdateDlg::OnDownloadEnded) 53 | END_EVENT_TABLE() 54 | 55 | UpdateDlg::UpdateDlg(wxWindow* parent) 56 | : m_Recs(0), 57 | m_RecsCount(0), 58 | m_CurrFileSize(0), 59 | m_LastBlockSize(0), 60 | m_HasUpdated(false), 61 | m_FirstTimeCheck(true), 62 | m_Net(this, idNet, _T("http://devpaks.sourceforge.net/")) 63 | { 64 | //ctor 65 | wxXmlResource::Get()->LoadDialog(this, parent, _T("MainFrame")); 66 | CreateListColumns(); 67 | FillServers(); 68 | UpdateStatus(_("Ready"), 0); 69 | } 70 | 71 | UpdateDlg::~UpdateDlg() 72 | { 73 | //dtor 74 | delete[] m_Recs; 75 | m_RecsCount = 0; 76 | } 77 | 78 | void UpdateDlg::EndModal(int retCode) 79 | { 80 | if (!m_Net.IsConnected() || retCode != wxID_CANCEL) 81 | { 82 | wxDialog::EndModal(retCode); 83 | return; 84 | } 85 | 86 | if (m_Net.IsConnected()) 87 | m_Net.Abort(); 88 | } 89 | 90 | void UpdateDlg::CreateListColumns() 91 | { 92 | wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl); 93 | lst->InsertColumn(0, _("Title")); 94 | lst->InsertColumn(1, _("Version")); 95 | lst->InsertColumn(2, _("Installed")); 96 | lst->InsertColumn(3, _("Size"), wxLIST_FORMAT_RIGHT); 97 | 98 | lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3) - 2); // 1st column takes all remaining space 99 | lst->SetColumnWidth(1, 64); 100 | lst->SetColumnWidth(2, 64); 101 | lst->SetColumnWidth(3, 64); 102 | } 103 | 104 | void UpdateDlg::AddRecordToList(UpdateRec* rec) 105 | { 106 | if (!rec) 107 | return; 108 | wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl); 109 | int idx = lst->GetItemCount(); 110 | lst->InsertItem(idx, rec->title); 111 | lst->SetItem(idx, 1, rec->version); 112 | lst->SetItem(idx, 2, rec->installed_version); 113 | lst->SetItem(idx, 3, rec->size); 114 | } 115 | 116 | void UpdateDlg::SetListColumnText(int idx, int col, const wxString& text) 117 | { 118 | wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl); 119 | int index = idx == -1 ? lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) : idx; 120 | wxListItem it; 121 | it.m_itemId = index; 122 | it.m_col = col; 123 | it.m_mask = wxLIST_MASK_TEXT; 124 | it.m_text = text; 125 | lst->SetItem(it); 126 | } 127 | 128 | void UpdateDlg::UpdateStatus(const wxString& status, int curProgress, int maxProgress) 129 | { 130 | wxStaticText* lbl = XRCCTRL(*this, "lblStatus", wxStaticText); 131 | if (lbl->GetLabel() != status) 132 | lbl->SetLabel(status); 133 | if (curProgress != -1) 134 | XRCCTRL(*this, "gauProgress", wxGauge)->SetValue(curProgress); 135 | if (maxProgress != -1) 136 | XRCCTRL(*this, "gauProgress", wxGauge)->SetRange(maxProgress); 137 | } 138 | 139 | void UpdateDlg::EnableButtons(bool update, bool abort) 140 | { 141 | wxButton* btnCl = XRCCTRL(*this, "wxID_CANCEL", wxButton); 142 | 143 | btnCl->Enable(abort); 144 | // disable server list and cache checkbox while downloading 145 | XRCCTRL(*this, "cmbServer", wxComboBox)->Enable(!m_Net.IsConnected()); 146 | XRCCTRL(*this, "chkCache", wxCheckBox)->Enable(!m_Net.IsConnected()); 147 | 148 | wxYield(); 149 | } 150 | 151 | void UpdateDlg::FillGroups() 152 | { 153 | UpdateStatus(_("Parsing list of updates"), 0, m_RecsCount - 1); 154 | 155 | // get a list of unique group names 156 | wxArrayString groups; 157 | for (int i = 0; i < m_RecsCount; ++i) 158 | { 159 | for (unsigned int x = 0; x < m_Recs[i].groups.GetCount(); ++x) 160 | { 161 | if (m_Recs[i].groups[x].IsEmpty()) 162 | continue; 163 | if (groups.Index(m_Recs[i].groups[x]) == wxNOT_FOUND) 164 | { 165 | if (FilterRec(&m_Recs[i])) 166 | groups.Add(m_Recs[i].groups[x]); 167 | } 168 | } 169 | } 170 | 171 | // create the groups tree 172 | wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl); 173 | tree->Freeze(); 174 | tree->DeleteAllItems(); 175 | wxTreeItemId root = tree->AddRoot(_("All categories")); 176 | for (unsigned int i = 0; i < groups.GetCount(); ++i) 177 | { 178 | tree->AppendItem(root, groups[i]); 179 | } 180 | tree->SortChildren(root); 181 | tree->Thaw(); 182 | tree->Expand(root); 183 | tree->SelectItem(root); // this calls the event 184 | 185 | UpdateStatus(_("Done parsing list of updates"), 0); 186 | } 187 | 188 | void UpdateDlg::FillFiles(const wxTreeItemId& id) 189 | { 190 | wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl); 191 | wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl); 192 | lst->Freeze(); 193 | lst->ClearAll(); 194 | CreateListColumns(); 195 | 196 | wxString group = id == tree->GetRootItem() ? _T("") : tree->GetItemText(id); 197 | 198 | // add files belonging to group 199 | int counter = 0; 200 | for (int i = 0; i < m_RecsCount; ++i) 201 | { 202 | if (group.IsEmpty() || (!m_Recs[i].groups.IsEmpty() && m_Recs[i].groups.Index(group) != wxNOT_FOUND)) 203 | { 204 | // filter 205 | if (FilterRec(&m_Recs[i])) 206 | { 207 | AddRecordToList(&m_Recs[i]); 208 | ++counter; 209 | } 210 | } 211 | } 212 | lst->Thaw(); 213 | 214 | // select first item 215 | lst->SetItemState(0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); 216 | } 217 | 218 | void UpdateDlg::FillFileDetails(const wxListItem& id) 219 | { 220 | wxTextCtrl* txt = XRCCTRL(*this, "txtInfo", wxTextCtrl); 221 | txt->Clear(); 222 | 223 | UpdateRec* cur = GetRecFromListView(); 224 | if (!cur) 225 | { 226 | txt->Clear(); 227 | EnableButtons(); 228 | return; 229 | } 230 | txt->AppendText(_("Name: ") + cur->name + _T("\n")); 231 | // txt->AppendText(_("Server: ") + cur->remote_server + _T("\n")); 232 | // txt->AppendText(_("File: ") + cur->remote_file + _T("\n")); 233 | txt->AppendText(_("Version: ") + cur->version + _T("\n")); 234 | txt->AppendText(_("Size: ") + cur->size + _T("\n")); 235 | txt->AppendText(_("Date: ") + cur->date + _T("\n\n")); 236 | txt->AppendText(_("Description: \n")); 237 | txt->AppendText(cur->desc); 238 | 239 | txt->SetSelection(0, 0); 240 | txt->SetInsertionPoint(0); 241 | } 242 | 243 | void UpdateDlg::InternetUpdate(bool forceDownload) 244 | { 245 | UpdateStatus(_("Please wait...")); 246 | m_HasUpdated = false; 247 | m_Net.SetServer(GetCurrentServer()); 248 | 249 | EnableButtons(false); 250 | forceDownload = forceDownload || !XRCCTRL(*this, "chkCache", wxCheckBox)->GetValue(); 251 | 252 | bool forceDownloadMirrors = forceDownload || !wxFileExists(GetMirrorsFilename()); 253 | if (forceDownloadMirrors) 254 | { 255 | if (!m_Net.DownloadFile(_T("mirrors.cfg"), GetMirrorsFilename())) 256 | { 257 | UpdateStatus(_("Error downloading list of mirrors"), 0, 0); 258 | return; 259 | } 260 | else 261 | { 262 | FillServers(); 263 | m_Net.SetServer(GetCurrentServer()); // update server based on mirrors 264 | } 265 | } 266 | 267 | wxString config = GetConfFilename(); 268 | forceDownload = forceDownload || !wxFileExists(config); 269 | if (forceDownload && !m_Net.DownloadFile(_T("webupdate.conf"), config)) 270 | { 271 | UpdateStatus(_("Error downloading list of updates"), 0, 0); 272 | return; 273 | } 274 | else 275 | { 276 | IniParser ini; 277 | if (!ini.ParseFile(config)) 278 | { 279 | UpdateStatus(_("Failed to retrieve the list of updates"), 0, 0); 280 | return; 281 | } 282 | ini.Sort(); 283 | 284 | if (m_Recs) 285 | delete[] m_Recs; 286 | 287 | // remember to delete[] m_Recs when we 're done with it!!! 288 | // it's our responsibility once given to us 289 | m_Recs = ReadConf(ini, &m_RecsCount, GetCurrentServer(), GetPackagePath()); 290 | 291 | FillGroups(); 292 | } 293 | EnableButtons(); 294 | UpdateStatus(_("Ready"), 0, 0); 295 | 296 | m_HasUpdated = true; 297 | } 298 | 299 | void UpdateDlg::FillServers() 300 | { 301 | wxComboBox* cmb = XRCCTRL(*this, "cmbServer", wxComboBox); 302 | cmb->Clear(); 303 | m_Servers.Clear(); 304 | 305 | IniParser ini; 306 | ini.ParseFile(GetMirrorsFilename()); 307 | int group = ini.FindGroupByName(_T("WebUpdate mirrors")); 308 | for (int i = 0; group != -1 && i < ini.GetKeysCount(group); ++i) 309 | { 310 | cmb->Append(ini.GetKeyName(group, i)); 311 | m_Servers.Add(ini.GetKeyValue(group, i)); 312 | } 313 | if (cmb->GetCount() == 0) 314 | { 315 | cmb->Append(_("devpaks.org Community Devpaks")); 316 | m_Servers.Add(_T("http://devpaks.sourceforge.net/")); 317 | } 318 | cmb->SetSelection(0); 319 | } 320 | 321 | wxString UpdateDlg::GetConfFilename() 322 | { 323 | int server_hash = GetTextCRC32(GetCurrentServer().mb_str()); 324 | wxString config; 325 | config = ConfigManager::GetConfigFolder() + wxFILE_SEP_PATH; 326 | config.Printf(_T("%sdevpak_%x.conf"), config.c_str(), server_hash); 327 | return config; 328 | } 329 | 330 | wxString UpdateDlg::GetMirrorsFilename() const 331 | { 332 | wxString config; 333 | config = ConfigManager::GetConfigFolder() + wxFILE_SEP_PATH + _T("devpak_mirrors.cfg"); 334 | return config; 335 | } 336 | 337 | wxString UpdateDlg::GetCurrentServer() const 338 | { 339 | return m_Servers[XRCCTRL(*this, "cmbServer", wxComboBox)->GetSelection()]; 340 | } 341 | 342 | wxString UpdateDlg::GetBasePath() const 343 | { 344 | return g_MasterPath + wxFILE_SEP_PATH; 345 | } 346 | 347 | wxString UpdateDlg::GetPackagePath() const 348 | { 349 | return GetBasePath() + _T("Packages") + wxFILE_SEP_PATH; 350 | } 351 | 352 | bool UpdateDlg::FilterRec(UpdateRec* rec) 353 | { 354 | if (!rec) 355 | return false; 356 | wxComboBox* cmb = XRCCTRL(*this, "cmbFilter", wxComboBox); 357 | switch (cmb->GetSelection()) 358 | { 359 | case 0: // All 360 | return true; 361 | 362 | case 1: // Installed 363 | return rec->installed; 364 | 365 | case 2: // installed with update available 366 | return rec->installed && rec->version != rec->installed_version; 367 | 368 | case 3: // downloaded but not installed 369 | return rec->downloaded && !rec->installed; 370 | 371 | case 4: // not installed 372 | return !rec->downloaded && !rec->installed; 373 | 374 | default: 375 | return false; 376 | } 377 | return false; // doesn't reach here 378 | } 379 | 380 | void UpdateDlg::ApplyFilter() 381 | { 382 | wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl); 383 | 384 | FillGroups(); 385 | FillFiles(tree->GetSelection()); 386 | EnableButtons(); 387 | } 388 | 389 | UpdateRec* UpdateDlg::GetRecFromListView() 390 | { 391 | wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl); 392 | int index = lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); 393 | if (index == -1) 394 | return 0; 395 | wxString title = lst->GetItemText(index); 396 | return FindRecByTitle(title, m_Recs, m_RecsCount); 397 | } 398 | 399 | void UpdateDlg::DownloadFile(bool dontInstall) 400 | { 401 | UpdateStatus(_("Please wait...")); 402 | UpdateRec* rec = GetRecFromListView(); 403 | if (!rec) 404 | { 405 | wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR); 406 | UpdateStatus(_("Ready"), 0, 0); 407 | return; 408 | } 409 | 410 | if (rec->version == rec->installed_version) 411 | { 412 | if (wxMessageBox(_("You seem to have installed the latest version.\nAre you sure you want to proceed?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxNO) 413 | return; 414 | } 415 | 416 | if (!CreateDirRecursively(GetPackagePath())) 417 | { 418 | wxMessageBox(_("Can't create directory ") + GetPackagePath(), _("Error"), wxICON_ERROR); 419 | return; 420 | } 421 | 422 | if (wxFileExists(GetPackagePath() + rec->local_file)) 423 | { 424 | if (wxMessageBox(_("This file already exists!\nAre you sure you want to download it again?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxNO && 425 | rec->installable) 426 | { 427 | if (!dontInstall && wxMessageBox(_("Do you want to force-install it?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES) 428 | InstallFile(); 429 | return; 430 | } 431 | } 432 | 433 | m_Net.SetServer(rec->remote_server); 434 | 435 | EnableButtons(false); 436 | if (!m_Net.DownloadFile(rec->remote_file, GetPackagePath() + rec->local_file)) 437 | { 438 | rec->downloaded = false; 439 | UpdateStatus(_("Error downloading file: ") + rec->remote_server + _T(" > ") + rec->remote_file, 0, 0); 440 | return; 441 | } 442 | else 443 | rec->downloaded = true; 444 | UpdateStatus(_("Ready"), 0, 0); 445 | EnableButtons(); 446 | } 447 | 448 | void UpdateDlg::InstallFile() 449 | { 450 | UpdateStatus(_("Please wait...")); 451 | UpdateRec* rec = GetRecFromListView(); 452 | if (!rec) 453 | { 454 | wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR); 455 | UpdateStatus(_("Ready"), 0, 0); 456 | return; 457 | } 458 | wxYield(); 459 | 460 | if (rec->title == _T("WebUpdate Mirrors list")) 461 | { 462 | InstallMirrors(GetPackagePath() + rec->local_file); 463 | rec->installed = true; 464 | ApplyFilter(); 465 | UpdateStatus(_("Ready"), 0, 0); 466 | return; 467 | } 468 | else if (!rec->installable) 469 | { 470 | UpdateStatus(_("Ready"), 0, 0); 471 | return; 472 | } 473 | 474 | if (!CreateDirRecursively(GetPackagePath())) 475 | { 476 | UpdateStatus(_("Ready"), 0, 0); 477 | wxMessageBox(_("Can't create directory ") + GetPackagePath(), _("Error"), wxICON_ERROR); 478 | return; 479 | } 480 | 481 | wxArrayString files; 482 | DevPakInstaller inst; 483 | if (inst.Install(rec->name, GetPackagePath() + rec->local_file, GetBasePath(), &files)) 484 | { 485 | // wxFileName fname(GetPackagePath() + rec->local_file); 486 | // fname.SetExt("entry"); 487 | // fname.SetName(rec->title); 488 | // CreateEntryFile(rec, fname.GetFullPath(), files); 489 | CreateEntryFile(rec, GetPackagePath() + rec->entry, files); 490 | wxMessageBox(_("DevPak installed"), _("Message"), wxICON_INFORMATION); 491 | 492 | // refresh installed_version 493 | rec->installed = true; 494 | rec->installed_version = rec->version; 495 | SetListColumnText(-1, 2, rec->installed_version); 496 | } 497 | else 498 | { 499 | wxMessageBox(_("DevPak was not installed.\nStatus:\n") + inst.GetStatus(), _("Error"), wxICON_ERROR); 500 | } 501 | UpdateStatus(_("Ready"), 0, 0); 502 | } 503 | 504 | void UpdateDlg::InstallMirrors(const wxString& file) 505 | { 506 | if (!wxCopyFile(file, GetMirrorsFilename(), true)) 507 | wxMessageBox(_("Can't install mirrors file: ") + file, _("Error"), wxICON_ERROR); 508 | else 509 | { 510 | wxRemoveFile(file); 511 | FillServers(); 512 | m_Net.SetServer(GetCurrentServer()); // update server based on mirrors 513 | wxMessageBox(_("Mirrors installed"), _("Information"), wxICON_INFORMATION); 514 | } 515 | } 516 | 517 | void UpdateDlg::UninstallFile() 518 | { 519 | UpdateStatus(_("Please wait...")); 520 | UpdateRec* rec = GetRecFromListView(); 521 | if (!rec) 522 | { 523 | wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR); 524 | UpdateStatus(_("Ready"), 0, 0); 525 | return; 526 | } 527 | wxYield(); 528 | 529 | DevPakInstaller inst; 530 | if (inst.Uninstall(GetPackagePath() + rec->entry)) 531 | { 532 | wxMessageBox(_("DevPak uninstalled"), _("Message"), wxICON_INFORMATION); 533 | 534 | // refresh installed_version 535 | rec->installed_version.Clear(); 536 | rec->installed = false; 537 | SetListColumnText(-1, 2, rec->installed_version); 538 | } 539 | else 540 | { 541 | wxMessageBox(_("DevPak was not uninstalled.\nStatus:\n") + inst.GetStatus(), _("Error"), wxICON_ERROR); 542 | } 543 | } 544 | 545 | void UpdateDlg::CreateEntryFile(UpdateRec* rec, const wxString& filename, const wxArrayString& files) 546 | { 547 | wxString entry; 548 | entry << _T("[Setup]\n"); 549 | entry << _T("AppName=") << rec->name << _T("\n"); 550 | entry << _T("AppVersion=") << rec->version << _T("\n"); 551 | 552 | entry << _T("\n"); 553 | entry << _T("[Files]\n"); 554 | for (unsigned int i = 0; i < files.GetCount(); ++i) 555 | { 556 | entry << files[i] << _T("\n"); 557 | } 558 | 559 | wxFile f(filename, wxFile::write); 560 | if (f.IsOpened()) 561 | { 562 | f.Write(entry.mb_str(wxConvUTF8),entry.Length()); 563 | } 564 | } 565 | 566 | void UpdateDlg::OnFileRightClick(wxListEvent& event) 567 | { 568 | // LOGSTREAM << "pt.x=" << event.GetPoint().x << ", pt.y=" << event.GetPoint().y << '\n'; 569 | UpdateRec* rec = GetRecFromListView(); 570 | if (!rec) 571 | return; 572 | 573 | wxMenu popup; 574 | popup.Append(idPopupDownloadAndInstall, _("Download && install")); 575 | popup.AppendSeparator(); 576 | popup.Append(idPopupDownload, _("Download")); 577 | popup.Append(idPopupInstall, _("Install")); 578 | popup.AppendSeparator(); 579 | popup.Append(idPopupUninstall, _("Uninstall")); 580 | 581 | bool canDl = !rec->downloaded || rec->version != rec->installed_version; 582 | bool canInst = rec->downloaded && (!rec->installed || rec->version != rec->installed_version); 583 | 584 | popup.Enable(idPopupDownload, canDl); 585 | popup.Enable(idPopupInstall, canInst); 586 | popup.Enable(idPopupDownloadAndInstall, canInst || canDl); 587 | popup.Enable(idPopupUninstall, rec->installed); 588 | 589 | wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl); 590 | lst->PopupMenu(&popup, event.GetPoint()); 591 | } 592 | 593 | void UpdateDlg::OnFileDeSelected(wxListEvent& event) 594 | { 595 | wxListItem id; 596 | FillFileDetails(id); 597 | EnableButtons(); 598 | } 599 | 600 | void UpdateDlg::OnFileSelected(wxListEvent& event) 601 | { 602 | FillFileDetails(event.GetItem()); 603 | EnableButtons(); 604 | } 605 | 606 | void UpdateDlg::OnTreeSelChanged(wxTreeEvent& event) 607 | { 608 | FillFiles(event.GetItem()); 609 | EnableButtons(); 610 | } 611 | 612 | void UpdateDlg::OnDownload(wxCommandEvent& event) 613 | { 614 | DownloadFile(true); 615 | } 616 | 617 | void UpdateDlg::OnInstall(wxCommandEvent& event) 618 | { 619 | InstallFile(); 620 | } 621 | 622 | void UpdateDlg::OnUninstall(wxCommandEvent& event) 623 | { 624 | UninstallFile(); 625 | } 626 | 627 | void UpdateDlg::OnDownloadAndInstall(wxCommandEvent& event) 628 | { 629 | DownloadFile(); 630 | } 631 | 632 | void UpdateDlg::OnServerChange(wxCommandEvent& event) 633 | { 634 | InternetUpdate(); 635 | } 636 | 637 | void UpdateDlg::OnFilterChange(wxCommandEvent& event) 638 | { 639 | ApplyFilter(); 640 | } 641 | 642 | void UpdateDlg::OnConnect(wxCommandEvent& event) 643 | { 644 | XRCCTRL(*this, "wxID_CANCEL", wxButton)->SetLabel(_("Abort")); 645 | EnableButtons(); 646 | } 647 | 648 | void UpdateDlg::OnDisConnect(wxCommandEvent& event) 649 | { 650 | XRCCTRL(*this, "wxID_CANCEL", wxButton)->SetLabel(_("Close")); 651 | EnableButtons(); 652 | } 653 | 654 | void UpdateDlg::OnProgress(wxCommandEvent& event) 655 | { 656 | int prg = -1; 657 | if (m_CurrFileSize != 0) 658 | prg = event.GetInt() * 100 / m_CurrFileSize; 659 | UpdateStatus(_("Downloading: ") + event.GetString(), prg); 660 | 661 | wxStaticText* lbl = XRCCTRL(*this, "lblProgress", wxStaticText); 662 | 663 | wxString msg; 664 | msg.Printf(_("%s of %s"), GetSizeString(event.GetInt()).c_str(), GetSizeString(m_CurrFileSize).c_str()); 665 | lbl->SetLabel(msg); 666 | } 667 | 668 | void UpdateDlg::OnAborted(wxCommandEvent& event) 669 | { 670 | UpdateStatus(_("Download aborted: ") + event.GetString(), 0, 0); 671 | XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T("")); 672 | m_LastBlockSize = 0; 673 | } 674 | 675 | void UpdateDlg::OnDownloadStarted(wxCommandEvent& event) 676 | { 677 | m_CurrFileSize = event.GetInt(); 678 | UpdateStatus(_("Download started: ") + event.GetString(), 0, 100); 679 | XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T("")); 680 | m_LastBlockSize = 0; 681 | } 682 | 683 | void UpdateDlg::OnDownloadEnded(wxCommandEvent& event) 684 | { 685 | UpdateStatus(_("Download finished: ") + event.GetString()); 686 | XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T("")); 687 | m_LastBlockSize = 0; 688 | 689 | if (m_HasUpdated && event.GetInt() == 0) 690 | { 691 | UpdateRec* rec = GetRecFromListView(); 692 | if (rec) 693 | { 694 | if (rec->bytes != m_CurrFileSize) 695 | wxMessageBox(_("File size mismatch for ") + event.GetString() + _("!\n\n" 696 | "This, usually, means one of three things:\n" 697 | "1) The reported size in the update list is wrong. The DevPak might still be valid.\n" 698 | "2) The file's location returned a web error-page. Invalid DevPak...\n" 699 | "3) The file is corrupt...\n\n" 700 | "You can try to install it anyway. If it is not a valid DevPak, the operation will fail."), 701 | _("Warning"), wxICON_WARNING); 702 | } 703 | if (rec && rec->installable && wxMessageBox(_("Do you want to install ") + event.GetString() + _(" now?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES) 704 | InstallFile(); 705 | else if (rec && rec->title == _T("WebUpdate Mirrors list")) 706 | InstallMirrors(GetPackagePath() + rec->local_file); 707 | } 708 | m_CurrFileSize = 0; 709 | } 710 | 711 | void UpdateDlg::OnUpdateUI(wxUpdateUIEvent& event) 712 | { 713 | // hack to display the download message *after* the dialog has been shown... 714 | if (m_FirstTimeCheck) 715 | { 716 | m_FirstTimeCheck = false; // no more, just once 717 | wxString config = GetConfFilename(); 718 | if (wxFileExists(config)) 719 | InternetUpdate(); 720 | else 721 | { 722 | if (wxMessageBox(_("A list of updates needs to be downloaded.\nDo you want to do this now?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES) 723 | InternetUpdate(true); 724 | } 725 | } 726 | } 727 | -------------------------------------------------------------------------------- /tests/01uni_multi/updatedlg.h: -------------------------------------------------------------------------------- 1 | #ifndef UPDATEDLG_H 2 | #define UPDATEDLG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "cbnetwork.h" 8 | #include "conf.h" 9 | 10 | class UpdateDlg : public wxDialog 11 | { 12 | public: 13 | UpdateDlg(wxWindow* parent); 14 | virtual ~UpdateDlg(); 15 | 16 | void EndModal(int retCode); 17 | protected: 18 | void OnFileSelected(wxListEvent& event); 19 | void OnFileDeSelected(wxListEvent& event); 20 | void OnFileRightClick(wxListEvent& event); 21 | void OnTreeSelChanged(wxTreeEvent& event); 22 | void OnDownload(wxCommandEvent& event); 23 | void OnInstall(wxCommandEvent& event); 24 | void OnUninstall(wxCommandEvent& event); 25 | void OnDownloadAndInstall(wxCommandEvent& event); 26 | void OnUpdate(wxCommandEvent& event); 27 | void OnServerChange(wxCommandEvent& event); 28 | void OnFilterChange(wxCommandEvent& event); 29 | void OnConnect(wxCommandEvent& event); 30 | void OnDisConnect(wxCommandEvent& event); 31 | void OnProgress(wxCommandEvent& event); 32 | void OnAborted(wxCommandEvent& event); 33 | void OnDownloadStarted(wxCommandEvent& event); 34 | void OnDownloadEnded(wxCommandEvent& event); 35 | void OnUpdateUI(wxUpdateUIEvent& event); 36 | private: 37 | void InternetUpdate(bool forceDownload = false); 38 | void DownloadFile(bool dontInstall = false); 39 | void InstallFile(); 40 | void UninstallFile(); 41 | void InstallMirrors(const wxString& file); 42 | void CreateEntryFile(UpdateRec* rec, const wxString& filename, const wxArrayString& files); 43 | void EnableButtons(bool update = true, bool abort = true); 44 | void FillServers(); 45 | void FillGroups(); 46 | void FillFiles(const wxTreeItemId& id); 47 | void FillFileDetails(const wxListItem& id); 48 | void UpdateStatus(const wxString& status, int curProgress = -1, int maxProgress = -1); 49 | UpdateRec* GetRecFromListView(); 50 | void CreateListColumns(); 51 | void AddRecordToList(UpdateRec* rec); 52 | void SetListColumnText(int idx, int col, const wxString& text); 53 | 54 | wxString GetConfFilename(); 55 | wxString GetMirrorsFilename() const; 56 | wxString GetCurrentServer() const; 57 | wxString GetBasePath() const; 58 | wxString GetPackagePath() const; 59 | bool FilterRec(UpdateRec* rec); 60 | void ApplyFilter(); 61 | 62 | UpdateRec* m_Recs; 63 | wxArrayString m_Servers; 64 | int m_RecsCount; 65 | int m_CurrFileSize; 66 | int m_LastBlockSize; // for bps 67 | bool m_HasUpdated; 68 | bool m_FirstTimeCheck; 69 | cbNetwork m_Net; 70 | DECLARE_EVENT_TABLE(); 71 | }; 72 | 73 | #endif // UPDATEDLG_H 74 | -------------------------------------------------------------------------------- /tests/02uni_newline.from: -------------------------------------------------------------------------------- 1 | 2 | read_patch("fix_devpak_install.patch") 3 | 4 | asd -------------------------------------------------------------------------------- /tests/02uni_newline.patch: -------------------------------------------------------------------------------- 1 | --- 02uni_newline.from 2008-07-02 18:34:04 +0000 2 | +++ 02uni_newline.to 2008-07-02 18:34:08 +0000 3 | @@ -1,4 +1,3 @@ 4 | 5 | read_patch("fix_devpak_install.patch") 6 | 7 | -asd 8 | \ No newline at end of file 9 | -------------------------------------------------------------------------------- /tests/02uni_newline.to: -------------------------------------------------------------------------------- 1 | 2 | read_patch("fix_devpak_install.patch") 3 | 4 | -------------------------------------------------------------------------------- /tests/03trail_fname.from: -------------------------------------------------------------------------------- 1 | Tests: 2 | - file not found 3 | - trailing spaces in patch filenames 4 | - already patched 5 | - create new files 6 | - remove files 7 | - svn diff 8 | - hg diff 9 | -------------------------------------------------------------------------------- /tests/03trail_fname.patch: -------------------------------------------------------------------------------- 1 | --- 03trail_fname.from 2 | +++ 03trail_fname.to 3 | @@ -1,7 +1,8 @@ 4 | Tests: 5 | - file not found 6 | -- trailing spaces in patch filenames 7 | - already patched 8 | + 9 | +Features: 10 | - create new files 11 | - remove files 12 | - svn diff 13 | -------------------------------------------------------------------------------- /tests/03trail_fname.to: -------------------------------------------------------------------------------- 1 | Tests: 2 | - file not found 3 | - already patched 4 | 5 | Features: 6 | - create new files 7 | - remove files 8 | - svn diff 9 | - hg diff 10 | -------------------------------------------------------------------------------- /tests/04can_patch.from: -------------------------------------------------------------------------------- 1 | beta 2 | beta 3 | beta 4 | beta 5 | beta 6 | beta 7 | beta 8 | alpha 9 | beta 10 | beta 11 | beta 12 | beta 13 | beta 14 | beta 15 | beta 16 | beta 17 | beta 18 | beta 19 | alpha 20 | beta 21 | beta 22 | beta 23 | beta 24 | beta 25 | beta 26 | beta 27 | beta 28 | beta 29 | beta 30 | beta 31 | beta 32 | beta 33 | beta 34 | beta 35 | beta 36 | beta 37 | beta 38 | beta 39 | beta 40 | beta -------------------------------------------------------------------------------- /tests/04can_patch.patch: -------------------------------------------------------------------------------- 1 | --- 04can_patch.from Sun Dec 27 09:53:51 2009 2 | +++ 04can_patch.to Sun Dec 27 09:54:06 2009 3 | @@ -6,8 +6,6 @@ 4 | beta 5 | beta 6 | alpha 7 | -beta 8 | -beta 9 | beta 10 | beta 11 | beta 12 | -------------------------------------------------------------------------------- /tests/04can_patch.to: -------------------------------------------------------------------------------- 1 | beta 2 | beta 3 | beta 4 | beta 5 | beta 6 | beta 7 | beta 8 | alpha 9 | beta 10 | beta 11 | beta 12 | beta 13 | beta 14 | beta 15 | beta 16 | beta 17 | alpha 18 | beta 19 | beta 20 | beta 21 | beta 22 | beta 23 | beta 24 | beta 25 | beta 26 | beta 27 | beta 28 | beta 29 | beta 30 | beta 31 | beta 32 | beta 33 | beta 34 | beta 35 | beta 36 | beta 37 | beta 38 | beta -------------------------------------------------------------------------------- /tests/05hg_change.from: -------------------------------------------------------------------------------- 1 | """ 2 | TestSuite 3 | 4 | Files/directories that comprise one test all have the same name, but a different extensions: 5 | *.patch 6 | *.from 7 | *.to 8 | 9 | *.doctest - self contained doctest patch 10 | 11 | TODO: recheck input/output sources 12 | 13 | == Code Coverage == 14 | 15 | To refresh code coverage stats, get 'coverage' tool from 16 | http://pypi.python.org/pypi/coverage/ and run this file with: 17 | 18 | coverage run run_tests.py 19 | coverage html -d coverage 20 | 21 | On Windows it may be more convenient instead of `coverage` call 22 | `python -m coverage.__main__` 23 | """ 24 | 25 | import os 26 | import sys 27 | import re 28 | import shutil 29 | import unittest 30 | import copy 31 | from os import listdir 32 | from os.path import abspath, dirname, exists, join, isdir 33 | from tempfile import mkdtemp 34 | 35 | verbose = False 36 | if "-v" in sys.argv or "--verbose" in sys.argv: 37 | verbose = True 38 | 39 | 40 | #: full path for directory with tests 41 | tests_dir = dirname(abspath(__file__)) 42 | 43 | 44 | # import patch_ng.py from parent directory 45 | save_path = sys.path 46 | sys.path.insert(0, dirname(tests_dir)) 47 | import patch_ng 48 | sys.path = save_path 49 | 50 | 51 | # ---------------------------------------------------------------------------- 52 | class TestPatchFiles(unittest.TestCase): 53 | """ 54 | unittest hack - test* methods are generated by add_test_methods() function 55 | below dynamicallt using information about *.patch files from tests directory 56 | 57 | """ 58 | def _assert_files_equal(self, file1, file2): 59 | f1 = f2 = None 60 | try: 61 | f1 = open(file1, "rb") 62 | f2 = open(file2, "rb") 63 | for line in f1: 64 | self.assertEqual(line, f2.readline()) 65 | 66 | finally: 67 | if f2: 68 | f2.close() 69 | if f1: 70 | f1.close() 71 | 72 | def _assert_dirs_equal(self, dir1, dir2, ignore=[]): 73 | """ compare dir1 with reference dir2 74 | .svn dirs are ignored 75 | 76 | """ 77 | # recursion here 78 | e2list = listdir(dir2) 79 | for e1 in listdir(dir1): 80 | if e1 == ".svn": 81 | continue 82 | e1path = join(dir1, e1) 83 | e2path = join(dir2, e1) 84 | self.assert_(exists(e1path)) 85 | self.assert_(exists(e2path), "%s does not exist" % e2path) 86 | self.assert_(isdir(e1path) == isdir(e2path)) 87 | if not isdir(e1path): 88 | self._assert_files_equal(e1path, e2path) 89 | else: 90 | self._assert_dirs_equal(e1path, e2path) 91 | e2list.remove(e1) 92 | for e2 in e2list: 93 | if e2 == ".svn" or e2 in ignore: 94 | continue 95 | self.fail("extra file or directory: %s" % e2) 96 | 97 | 98 | def _run_test(self, testname): 99 | """ 100 | boilerplate for running *.patch file tests 101 | """ 102 | 103 | # 1. create temp test directory 104 | # 2. copy files 105 | # 3. execute file-based patch 106 | # 4. compare results 107 | # 5. cleanup on success 108 | 109 | tmpdir = mkdtemp(prefix="%s."%testname) 110 | 111 | patch_file = join(tmpdir, "%s.patch" % testname) 112 | shutil.copy(join(tests_dir, "%s.patch" % testname), patch_file) 113 | 114 | from_src = join(tests_dir, "%s.from" % testname) 115 | from_tgt = join(tmpdir, "%s.from" % testname) 116 | 117 | if not isdir(from_src): 118 | shutil.copy(from_src, from_tgt) 119 | else: 120 | for e in listdir(from_src): 121 | if e == ".svn": 122 | continue 123 | epath = join(from_src, e) 124 | if not isdir(epath): 125 | shutil.copy(epath, join(tmpdir, e)) 126 | else: 127 | shutil.copytree(epath, join(tmpdir, e)) 128 | 129 | 130 | # 3. 131 | # test utility as a whole 132 | patch_tool = join(dirname(tests_dir), "patch_ng.py") 133 | save_cwd = os.getcwdu() 134 | os.chdir(tmpdir) 135 | if verbose: 136 | cmd = '%s %s "%s"' % (sys.executable, patch_tool, patch_file) 137 | print "\n"+cmd 138 | else: 139 | cmd = '%s %s -q "%s"' % (sys.executable, patch_tool, patch_file) 140 | ret = os.system(cmd) 141 | assert ret == 0, "Error %d running test %s" % (ret, testname) 142 | os.chdir(save_cwd) 143 | 144 | 145 | # 4. 146 | # compare results 147 | if not isdir(from_src): 148 | self._assert_files_equal(join(tests_dir, "%s.to" % testname), from_tgt) 149 | else: 150 | # need recursive compare 151 | self._assert_dirs_equal(join(tests_dir, "%s.to" % testname), tmpdir, "%s.patch" % testname) 152 | 153 | 154 | 155 | shutil.rmtree(tmpdir) 156 | return 0 157 | 158 | 159 | def add_test_methods(cls): 160 | """ 161 | hack to generate test* methods in target class - one 162 | for each *.patch file in tests directory 163 | """ 164 | 165 | # list testcases - every test starts with number 166 | # and add them as test* methods 167 | testptn = re.compile(r"^(?P\d{2,}.+)\.(?P[^\.]+)") 168 | testset = sorted( set([testptn.match(e).group('name') for e in listdir(tests_dir) if testptn.match(e)]) ) 169 | 170 | for filename in testset: 171 | methname = filename.replace(" ", "_") 172 | def create_closure(): 173 | name = filename 174 | return lambda self: self._run_test(name) 175 | setattr(cls, "test%s" % methname, create_closure()) 176 | if verbose: 177 | print "added test method %s to %s" % (methname, cls) 178 | add_test_methods(TestPatchFiles) 179 | 180 | # ---------------------------------------------------------------------------- 181 | 182 | class TestCheckPatched(unittest.TestCase): 183 | def setUp(self): 184 | self.save_cwd = os.getcwdu() 185 | os.chdir(tests_dir) 186 | 187 | def tearDown(self): 188 | os.chdir(self.save_cwd) 189 | 190 | def test_patched_multiline(self): 191 | pto = patch.fromfile("01uni_multi.patch") 192 | os.chdir(join(tests_dir, "01uni_multi.to")) 193 | self.assert_(pto.can_patch("updatedlg.cpp")) 194 | 195 | def test_can_patch_single_source(self): 196 | pto2 = patch.fromfile("02uni_newline.patch") 197 | self.assert_(pto2.can_patch("02uni_newline.from")) 198 | 199 | def test_can_patch_fails_on_target_file(self): 200 | pto3 = patch.fromfile("03trail_fname.patch") 201 | self.assertEqual(None, pto3.can_patch("03trail_fname.to")) 202 | self.assertEqual(None, pto3.can_patch("not_in_source.also")) 203 | 204 | def test_multiline_false_on_other_file(self): 205 | pto = patch.fromfile("01uni_multi.patch") 206 | os.chdir(join(tests_dir, "01uni_multi.from")) 207 | self.assertFalse(pto.can_patch("updatedlg.cpp")) 208 | 209 | def test_single_false_on_other_file(self): 210 | pto3 = patch.fromfile("03trail_fname.patch") 211 | self.assertFalse(pto3.can_patch("03trail_fname.from")) 212 | 213 | def test_can_patch_checks_source_filename_even_if_target_can_be_patched(self): 214 | pto2 = patch.fromfile("04can_patch.patch") 215 | self.assertFalse(pto2.can_patch("04can_patch.to")) 216 | 217 | # ---------------------------------------------------------------------------- 218 | 219 | class TestPatchParse(unittest.TestCase): 220 | def test_fromstring(self): 221 | try: 222 | f = open(join(tests_dir, "01uni_multi.patch"), "rb") 223 | readstr = f.read() 224 | finally: 225 | f.close() 226 | pst = patch.fromstring(readstr) 227 | self.assertEqual(len(pst), 5) 228 | 229 | def test_no_header_for_plain_diff_with_single_file(self): 230 | pto = patch.fromfile(join(tests_dir, "03trail_fname.patch")) 231 | self.assertEqual(pto.items[0].header, []) 232 | 233 | def test_header_for_second_file_in_svn_diff(self): 234 | pto = patch.fromfile(join(tests_dir, "01uni_multi.patch")) 235 | self.assertEqual(pto.items[1].header[0], 'Index: updatedlg.h\r\n') 236 | self.assert_(pto.items[1].header[1].startswith('=====')) 237 | 238 | def test_fail_missing_hunk_line(self): 239 | fp = open(join(tests_dir, "data/failing/missing-hunk-line.diff")) 240 | pto = patch.PatchSet() 241 | self.assertNotEqual(pto.parse(fp), True) 242 | fp.close() 243 | 244 | def test_fail_absolute_path(self): 245 | fp = open(join(tests_dir, "data/failing/absolute-path.diff")) 246 | res = patch.PatchSet().parse(fp) 247 | self.assertFalse(res) 248 | fp.close() 249 | 250 | def test_fail_parent_path(self): 251 | fp = open(join(tests_dir, "data/failing/parent-path.diff")) 252 | res = patch.PatchSet().parse(fp) 253 | self.assertFalse(res) 254 | fp.close() 255 | 256 | class TestPatchSetDetect(unittest.TestCase): 257 | def test_svn_detected(self): 258 | pto = patch.fromfile(join(tests_dir, "01uni_multi.patch")) 259 | self.assertEqual(pto.type, patch.SVN) 260 | 261 | def test_hg_detected(self): 262 | pto = patch.fromfile(join(tests_dir, "data/hg-added-file.diff")) 263 | self.assertEqual(pto.type, patch.HG) 264 | 265 | class TestPatchApply(unittest.TestCase): 266 | def setUp(self): 267 | self.save_cwd = os.getcwdu() 268 | self.tmpdir = mkdtemp(prefix=self.__class__.__name__) 269 | os.chdir(self.tmpdir) 270 | 271 | def tearDown(self): 272 | os.chdir(self.save_cwd) 273 | shutil.rmtree(self.tmpdir) 274 | 275 | def tmpcopy(self, filenames): 276 | """copy file(s) from test_dir to self.tmpdir""" 277 | for f in filenames: 278 | shutil.copy(join(tests_dir, f), self.tmpdir) 279 | 280 | def test_apply_returns_false_of_failure(self): 281 | self.tmpcopy(['data/failing/non-empty-patch-for-empty-file.diff', 282 | 'data/failing/upload.py']) 283 | pto = patch.fromfile('non-empty-patch-for-empty-file.diff') 284 | self.assertFalse(pto.apply()) 285 | 286 | def test_apply_returns_true_on_success(self): 287 | self.tmpcopy(['03trail_fname.patch', 288 | '03trail_fname.from']) 289 | pto = patch.fromfile('03trail_fname.patch') 290 | self.assert_(pto.apply()) 291 | 292 | # ---------------------------------------------------------------------------- 293 | 294 | if __name__ == '__main__': 295 | unittest.main() 296 | -------------------------------------------------------------------------------- /tests/05hg_change.patch: -------------------------------------------------------------------------------- 1 | diff -r 603f07175741 05hg_change.from 2 | --- a/05hg_change.from Wed Mar 16 08:21:44 2011 +0200 3 | +++ b/05hg_change.to Wed Mar 16 10:08:57 2011 +0200 4 | @@ -262,6 +262,10 @@ 5 | pto = patch.fromfile(join(tests_dir, "data/hg-added-file.diff")) 6 | self.assertEqual(pto.type, patch.HG) 7 | 8 | + def test_git_changed_detected(self): 9 | + pto = patch.fromfile(join(tests_dir, "data/git-changed-file.diff")) 10 | + self.assertEqual(pto.type, patch.GIT) 11 | + 12 | class TestPatchApply(unittest.TestCase): 13 | def setUp(self): 14 | self.save_cwd = os.getcwdu() 15 | -------------------------------------------------------------------------------- /tests/05hg_change.to: -------------------------------------------------------------------------------- 1 | """ 2 | TestSuite 3 | 4 | Files/directories that comprise one test all have the same name, but a different extensions: 5 | *.patch 6 | *.from 7 | *.to 8 | 9 | *.doctest - self contained doctest patch 10 | 11 | TODO: recheck input/output sources 12 | 13 | == Code Coverage == 14 | 15 | To refresh code coverage stats, get 'coverage' tool from 16 | http://pypi.python.org/pypi/coverage/ and run this file with: 17 | 18 | coverage run run_tests.py 19 | coverage html -d coverage 20 | 21 | On Windows it may be more convenient instead of `coverage` call 22 | `python -m coverage.__main__` 23 | """ 24 | 25 | import os 26 | import sys 27 | import re 28 | import shutil 29 | import unittest 30 | import copy 31 | from os import listdir 32 | from os.path import abspath, dirname, exists, join, isdir 33 | from tempfile import mkdtemp 34 | 35 | verbose = False 36 | if "-v" in sys.argv or "--verbose" in sys.argv: 37 | verbose = True 38 | 39 | 40 | #: full path for directory with tests 41 | tests_dir = dirname(abspath(__file__)) 42 | 43 | 44 | # import patch_ng.py from parent directory 45 | save_path = sys.path 46 | sys.path.insert(0, dirname(tests_dir)) 47 | import patch_ng 48 | sys.path = save_path 49 | 50 | 51 | # ---------------------------------------------------------------------------- 52 | class TestPatchFiles(unittest.TestCase): 53 | """ 54 | unittest hack - test* methods are generated by add_test_methods() function 55 | below dynamicallt using information about *.patch files from tests directory 56 | 57 | """ 58 | def _assert_files_equal(self, file1, file2): 59 | f1 = f2 = None 60 | try: 61 | f1 = open(file1, "rb") 62 | f2 = open(file2, "rb") 63 | for line in f1: 64 | self.assertEqual(line, f2.readline()) 65 | 66 | finally: 67 | if f2: 68 | f2.close() 69 | if f1: 70 | f1.close() 71 | 72 | def _assert_dirs_equal(self, dir1, dir2, ignore=[]): 73 | """ compare dir1 with reference dir2 74 | .svn dirs are ignored 75 | 76 | """ 77 | # recursion here 78 | e2list = listdir(dir2) 79 | for e1 in listdir(dir1): 80 | if e1 == ".svn": 81 | continue 82 | e1path = join(dir1, e1) 83 | e2path = join(dir2, e1) 84 | self.assert_(exists(e1path)) 85 | self.assert_(exists(e2path), "%s does not exist" % e2path) 86 | self.assert_(isdir(e1path) == isdir(e2path)) 87 | if not isdir(e1path): 88 | self._assert_files_equal(e1path, e2path) 89 | else: 90 | self._assert_dirs_equal(e1path, e2path) 91 | e2list.remove(e1) 92 | for e2 in e2list: 93 | if e2 == ".svn" or e2 in ignore: 94 | continue 95 | self.fail("extra file or directory: %s" % e2) 96 | 97 | 98 | def _run_test(self, testname): 99 | """ 100 | boilerplate for running *.patch file tests 101 | """ 102 | 103 | # 1. create temp test directory 104 | # 2. copy files 105 | # 3. execute file-based patch 106 | # 4. compare results 107 | # 5. cleanup on success 108 | 109 | tmpdir = mkdtemp(prefix="%s."%testname) 110 | 111 | patch_file = join(tmpdir, "%s.patch" % testname) 112 | shutil.copy(join(tests_dir, "%s.patch" % testname), patch_file) 113 | 114 | from_src = join(tests_dir, "%s.from" % testname) 115 | from_tgt = join(tmpdir, "%s.from" % testname) 116 | 117 | if not isdir(from_src): 118 | shutil.copy(from_src, from_tgt) 119 | else: 120 | for e in listdir(from_src): 121 | if e == ".svn": 122 | continue 123 | epath = join(from_src, e) 124 | if not isdir(epath): 125 | shutil.copy(epath, join(tmpdir, e)) 126 | else: 127 | shutil.copytree(epath, join(tmpdir, e)) 128 | 129 | 130 | # 3. 131 | # test utility as a whole 132 | patch_tool = join(dirname(tests_dir), "patch_ng.py") 133 | save_cwd = os.getcwdu() 134 | os.chdir(tmpdir) 135 | if verbose: 136 | cmd = '%s %s "%s"' % (sys.executable, patch_tool, patch_file) 137 | print "\n"+cmd 138 | else: 139 | cmd = '%s %s -q "%s"' % (sys.executable, patch_tool, patch_file) 140 | ret = os.system(cmd) 141 | assert ret == 0, "Error %d running test %s" % (ret, testname) 142 | os.chdir(save_cwd) 143 | 144 | 145 | # 4. 146 | # compare results 147 | if not isdir(from_src): 148 | self._assert_files_equal(join(tests_dir, "%s.to" % testname), from_tgt) 149 | else: 150 | # need recursive compare 151 | self._assert_dirs_equal(join(tests_dir, "%s.to" % testname), tmpdir, "%s.patch" % testname) 152 | 153 | 154 | 155 | shutil.rmtree(tmpdir) 156 | return 0 157 | 158 | 159 | def add_test_methods(cls): 160 | """ 161 | hack to generate test* methods in target class - one 162 | for each *.patch file in tests directory 163 | """ 164 | 165 | # list testcases - every test starts with number 166 | # and add them as test* methods 167 | testptn = re.compile(r"^(?P\d{2,}.+)\.(?P[^\.]+)") 168 | testset = sorted( set([testptn.match(e).group('name') for e in listdir(tests_dir) if testptn.match(e)]) ) 169 | 170 | for filename in testset: 171 | methname = filename.replace(" ", "_") 172 | def create_closure(): 173 | name = filename 174 | return lambda self: self._run_test(name) 175 | setattr(cls, "test%s" % methname, create_closure()) 176 | if verbose: 177 | print "added test method %s to %s" % (methname, cls) 178 | add_test_methods(TestPatchFiles) 179 | 180 | # ---------------------------------------------------------------------------- 181 | 182 | class TestCheckPatched(unittest.TestCase): 183 | def setUp(self): 184 | self.save_cwd = os.getcwdu() 185 | os.chdir(tests_dir) 186 | 187 | def tearDown(self): 188 | os.chdir(self.save_cwd) 189 | 190 | def test_patched_multiline(self): 191 | pto = patch.fromfile("01uni_multi.patch") 192 | os.chdir(join(tests_dir, "01uni_multi.to")) 193 | self.assert_(pto.can_patch("updatedlg.cpp")) 194 | 195 | def test_can_patch_single_source(self): 196 | pto2 = patch.fromfile("02uni_newline.patch") 197 | self.assert_(pto2.can_patch("02uni_newline.from")) 198 | 199 | def test_can_patch_fails_on_target_file(self): 200 | pto3 = patch.fromfile("03trail_fname.patch") 201 | self.assertEqual(None, pto3.can_patch("03trail_fname.to")) 202 | self.assertEqual(None, pto3.can_patch("not_in_source.also")) 203 | 204 | def test_multiline_false_on_other_file(self): 205 | pto = patch.fromfile("01uni_multi.patch") 206 | os.chdir(join(tests_dir, "01uni_multi.from")) 207 | self.assertFalse(pto.can_patch("updatedlg.cpp")) 208 | 209 | def test_single_false_on_other_file(self): 210 | pto3 = patch.fromfile("03trail_fname.patch") 211 | self.assertFalse(pto3.can_patch("03trail_fname.from")) 212 | 213 | def test_can_patch_checks_source_filename_even_if_target_can_be_patched(self): 214 | pto2 = patch.fromfile("04can_patch.patch") 215 | self.assertFalse(pto2.can_patch("04can_patch.to")) 216 | 217 | # ---------------------------------------------------------------------------- 218 | 219 | class TestPatchParse(unittest.TestCase): 220 | def test_fromstring(self): 221 | try: 222 | f = open(join(tests_dir, "01uni_multi.patch"), "rb") 223 | readstr = f.read() 224 | finally: 225 | f.close() 226 | pst = patch.fromstring(readstr) 227 | self.assertEqual(len(pst), 5) 228 | 229 | def test_no_header_for_plain_diff_with_single_file(self): 230 | pto = patch.fromfile(join(tests_dir, "03trail_fname.patch")) 231 | self.assertEqual(pto.items[0].header, []) 232 | 233 | def test_header_for_second_file_in_svn_diff(self): 234 | pto = patch.fromfile(join(tests_dir, "01uni_multi.patch")) 235 | self.assertEqual(pto.items[1].header[0], 'Index: updatedlg.h\r\n') 236 | self.assert_(pto.items[1].header[1].startswith('=====')) 237 | 238 | def test_fail_missing_hunk_line(self): 239 | fp = open(join(tests_dir, "data/failing/missing-hunk-line.diff")) 240 | pto = patch.PatchSet() 241 | self.assertNotEqual(pto.parse(fp), True) 242 | fp.close() 243 | 244 | def test_fail_absolute_path(self): 245 | fp = open(join(tests_dir, "data/failing/absolute-path.diff")) 246 | res = patch.PatchSet().parse(fp) 247 | self.assertFalse(res) 248 | fp.close() 249 | 250 | def test_fail_parent_path(self): 251 | fp = open(join(tests_dir, "data/failing/parent-path.diff")) 252 | res = patch.PatchSet().parse(fp) 253 | self.assertFalse(res) 254 | fp.close() 255 | 256 | class TestPatchSetDetect(unittest.TestCase): 257 | def test_svn_detected(self): 258 | pto = patch.fromfile(join(tests_dir, "01uni_multi.patch")) 259 | self.assertEqual(pto.type, patch.SVN) 260 | 261 | def test_hg_detected(self): 262 | pto = patch.fromfile(join(tests_dir, "data/hg-added-file.diff")) 263 | self.assertEqual(pto.type, patch.HG) 264 | 265 | def test_git_changed_detected(self): 266 | pto = patch.fromfile(join(tests_dir, "data/git-changed-file.diff")) 267 | self.assertEqual(pto.type, patch.GIT) 268 | 269 | class TestPatchApply(unittest.TestCase): 270 | def setUp(self): 271 | self.save_cwd = os.getcwdu() 272 | self.tmpdir = mkdtemp(prefix=self.__class__.__name__) 273 | os.chdir(self.tmpdir) 274 | 275 | def tearDown(self): 276 | os.chdir(self.save_cwd) 277 | shutil.rmtree(self.tmpdir) 278 | 279 | def tmpcopy(self, filenames): 280 | """copy file(s) from test_dir to self.tmpdir""" 281 | for f in filenames: 282 | shutil.copy(join(tests_dir, f), self.tmpdir) 283 | 284 | def test_apply_returns_false_of_failure(self): 285 | self.tmpcopy(['data/failing/non-empty-patch-for-empty-file.diff', 286 | 'data/failing/upload.py']) 287 | pto = patch.fromfile('non-empty-patch-for-empty-file.diff') 288 | self.assertFalse(pto.apply()) 289 | 290 | def test_apply_returns_true_on_success(self): 291 | self.tmpcopy(['03trail_fname.patch', 292 | '03trail_fname.from']) 293 | pto = patch.fromfile('03trail_fname.patch') 294 | self.assert_(pto.apply()) 295 | 296 | # ---------------------------------------------------------------------------- 297 | 298 | if __name__ == '__main__': 299 | unittest.main() 300 | -------------------------------------------------------------------------------- /tests/06nested/.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | *.pyc 3 | *.pyo 4 | build 5 | pyglet.*.log 6 | *.orig 7 | .DS_Store 8 | doc/api 9 | doc/_build 10 | doc/internal/build.rst 11 | website/dist 12 | -------------------------------------------------------------------------------- /tests/06nested/06nested.patch: -------------------------------------------------------------------------------- 1 | diff -r dcee1189d959 .hgignore 2 | --- a/.hgignore Tue Oct 23 01:05:32 2012 +0300 3 | +++ b/.hgignore Mon Dec 03 11:32:26 2012 +0300 4 | @@ -5,6 +5,7 @@ 5 | pyglet.*.log 6 | *.orig 7 | .DS_Store 8 | +_htmlapi 9 | doc/api 10 | doc/_build 11 | doc/internal/build.rst 12 | diff -r dcee1189d959 examples/font_comparison.py 13 | --- a/examples/font_comparison.py Tue Oct 23 01:05:32 2012 +0300 14 | +++ b/examples/font_comparison.py Mon Dec 03 10:06:28 2012 +0300 15 | @@ -45,13 +45,14 @@ 16 | FONTS = ['Andale Mono', 'Consolas', 'Inconsolata', 'Inconsolata-dz', 'Monaco', 17 | 'Menlo'] 18 | 19 | -SAMPLE = '''class Spam(object): 20 | - def __init__(self): 21 | - # The quick brown fox 22 | - self.spam = {"jumped": 'over'} 23 | +SAMPLE = '''\ 24 | +class Spam(object): 25 | + def __init__(self): 26 | + # The quick brown fox 27 | + self.spam = {"jumped": 'over'} 28 | @the 29 | - def lazy(self, *dog): 30 | - self.dog = [lazy, lazy]''' 31 | + def lazy(self, *dog): 32 | + self.dog = [lazy, lazy]''' 33 | 34 | class Window(pyglet.window.Window): 35 | font_num = 0 36 | diff -r dcee1189d959 experimental/console.py 37 | --- a/experimental/console.py Tue Oct 23 01:05:32 2012 +0300 38 | +++ b/experimental/console.py Mon Dec 03 10:06:28 2012 +0300 39 | @@ -18,7 +18,7 @@ 40 | 41 | class Console(object): 42 | def __init__(self, width, height, globals=None, locals=None): 43 | - self.font = pyglet.text.default_font_factory.get_font('bitstream vera sans mono', 12) 44 | + self.font = pyglet.font.load('bitstream vera sans mono', 12) 45 | self.lines = [] 46 | self.buffer = '' 47 | self.pre_buffer = '' 48 | @@ -29,7 +29,7 @@ 49 | self.write_pending = '' 50 | 51 | self.width, self.height = (width, height) 52 | - self.max_lines = self.height / self.font.glyph_height - 1 53 | + self.max_lines = self.height / (self.font.ascent - self.font.descent) - 1 54 | 55 | self.write('pyglet command console\n') 56 | self.write('Version %s\n' % __version__) 57 | diff -r dcee1189d959 pyglet/font/win32.py 58 | --- a/pyglet/font/win32.py Tue Oct 23 01:05:32 2012 +0300 59 | +++ b/pyglet/font/win32.py Mon Dec 03 10:06:28 2012 +0300 60 | @@ -45,6 +45,7 @@ 61 | from sys import byteorder 62 | import pyglet 63 | from pyglet.font import base 64 | +from pyglet.font import win32query 65 | import pyglet.image 66 | from pyglet.libs.win32.constants import * 67 | from pyglet.libs.win32.types import * 68 | @@ -262,9 +263,7 @@ 69 | 70 | @classmethod 71 | def have_font(cls, name): 72 | - # CreateFontIndirect always returns a font... have to work out 73 | - # something with EnumFontFamily... TODO 74 | - return True 75 | + return win32query.have_font(name) 76 | 77 | @classmethod 78 | def add_font_data(cls, data): 79 | diff -r dcee1189d959 tests/app/EVENT_LOOP.py 80 | --- a/tests/app/EVENT_LOOP.py Tue Oct 23 01:05:32 2012 +0300 81 | +++ b/tests/app/EVENT_LOOP.py Mon Dec 03 10:06:28 2012 +0300 82 | @@ -21,8 +21,8 @@ 83 | 84 | class EVENT_LOOP(unittest.TestCase): 85 | def t_scheduled(self, interval, iterations, sleep_time=0): 86 | - print 'Test interval=%s, iterations=%s, sleep=%s' % (interval, 87 | - iterations, sleep_time) 88 | + print('Test interval=%s, iterations=%s, sleep=%s' % (interval, 89 | + iterations, sleep_time)) 90 | warmup_iterations = iterations 91 | 92 | self.last_t = 0. 93 | @@ -76,6 +76,6 @@ 94 | 95 | if __name__ == '__main__': 96 | if pyglet.version != '1.2dev': 97 | - print 'Wrong version of pyglet imported; please check your PYTHONPATH' 98 | + print('Wrong version of pyglet imported; please check your PYTHONPATH') 99 | else: 100 | unittest.main() 101 | diff -r dcee1189d959 tests/font/SYSTEM.py 102 | --- a/tests/font/SYSTEM.py Tue Oct 23 01:05:32 2012 +0300 103 | +++ b/tests/font/SYSTEM.py Mon Dec 03 10:06:28 2012 +0300 104 | @@ -20,7 +20,7 @@ 105 | if sys.platform == 'darwin': 106 | font_name = 'Helvetica' 107 | elif sys.platform in ('win32', 'cygwin'): 108 | - font_name = 'Arial' 109 | + font_name = 'Modern No.20' 110 | else: 111 | font_name = 'Arial' 112 | 113 | -------------------------------------------------------------------------------- /tests/06nested/[result]/.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | *.pyc 3 | *.pyo 4 | build 5 | pyglet.*.log 6 | *.orig 7 | .DS_Store 8 | _htmlapi 9 | doc/api 10 | doc/_build 11 | doc/internal/build.rst 12 | website/dist 13 | -------------------------------------------------------------------------------- /tests/06nested/[result]/examples/font_comparison.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # ---------------------------------------------------------------------------- 3 | # pyglet 4 | # Copyright (c) 2006-2008 Alex Holkner 5 | # All rights reserved. 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions 9 | # are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in 15 | # the documentation and/or other materials provided with the 16 | # distribution. 17 | # * Neither the name of pyglet nor the names of its 18 | # contributors may be used to endorse or promote products 19 | # derived from this software without specific prior written 20 | # permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | # ---------------------------------------------------------------------------- 35 | 36 | '''A simple tool that may be used to compare font faces. 37 | 38 | Use the left/right cursor keys to change font faces. 39 | ''' 40 | 41 | __docformat__ = 'restructuredtext' 42 | __version__ = '$Id: $' 43 | import pyglet 44 | 45 | FONTS = ['Andale Mono', 'Consolas', 'Inconsolata', 'Inconsolata-dz', 'Monaco', 46 | 'Menlo'] 47 | 48 | SAMPLE = '''\ 49 | class Spam(object): 50 | def __init__(self): 51 | # The quick brown fox 52 | self.spam = {"jumped": 'over'} 53 | @the 54 | def lazy(self, *dog): 55 | self.dog = [lazy, lazy]''' 56 | 57 | class Window(pyglet.window.Window): 58 | font_num = 0 59 | def on_text_motion(self, motion): 60 | if motion == pyglet.window.key.MOTION_RIGHT: 61 | self.font_num += 1 62 | if self.font_num == len(FONTS): 63 | self.font_num = 0 64 | elif motion == pyglet.window.key.MOTION_LEFT: 65 | self.font_num -= 1 66 | if self.font_num < 0: 67 | self.font_num = len(FONTS) - 1 68 | 69 | face = FONTS[self.font_num] 70 | self.head = pyglet.text.Label(face, font_size=24, y=0, 71 | anchor_y='bottom') 72 | self.text = pyglet.text.Label(SAMPLE, font_name=face, font_size=18, 73 | y=self.height, anchor_y='top', width=self.width, multiline=True) 74 | 75 | def on_draw(self): 76 | self.clear() 77 | self.head.draw() 78 | self.text.draw() 79 | 80 | window = Window() 81 | window.on_text_motion(None) 82 | pyglet.app.run() 83 | -------------------------------------------------------------------------------- /tests/06nested/[result]/experimental/console.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | ''' 5 | 6 | __docformat__ = 'restructuredtext' 7 | __version__ = '$Id$' 8 | 9 | import code 10 | import sys 11 | import traceback 12 | 13 | import pyglet.event 14 | import pyglet.text 15 | from pyglet.window import key 16 | 17 | from pyglet.gl import * 18 | 19 | class Console(object): 20 | def __init__(self, width, height, globals=None, locals=None): 21 | self.font = pyglet.font.load('bitstream vera sans mono', 12) 22 | self.lines = [] 23 | self.buffer = '' 24 | self.pre_buffer = '' 25 | self.prompt = '>>> ' 26 | self.prompt2 = '... ' 27 | self.globals = globals 28 | self.locals = locals 29 | self.write_pending = '' 30 | 31 | self.width, self.height = (width, height) 32 | self.max_lines = self.height / (self.font.ascent - self.font.descent) - 1 33 | 34 | self.write('pyglet command console\n') 35 | self.write('Version %s\n' % __version__) 36 | 37 | def on_key_press(self, symbol, modifiers): 38 | # TODO cursor control / line editing 39 | if modifiers & key.key.MOD_CTRL and symbol == key.key.C: 40 | self.buffer = '' 41 | self.pre_buffer = '' 42 | return 43 | if symbol == key.key.ENTER: 44 | self.write('%s%s\n' % (self.get_prompt(), self.buffer)) 45 | self.execute(self.pre_buffer + self.buffer) 46 | self.buffer = '' 47 | return 48 | if symbol == key.key.BACKSPACE: 49 | self.buffer = self.buffer[:-1] 50 | return 51 | return EVENT_UNHANDLED 52 | 53 | def on_text(self, text): 54 | if ' ' <= text <= '~': 55 | self.buffer += text 56 | if 0xae <= ord(text) <= 0xff: 57 | self.buffer += text 58 | 59 | def write(self, text): 60 | if self.write_pending: 61 | text = self.write_pending + text 62 | self.write_pending = '' 63 | 64 | if type(text) in (str, unicode): 65 | text = text.split('\n') 66 | 67 | if text[-1] != '': 68 | self.write_pending = text[-1] 69 | del text[-1] 70 | 71 | self.lines = [pyglet.text.layout_text(line.strip(), font=self.font) 72 | for line in text] + self.lines 73 | 74 | if len(self.lines) > self.max_lines: 75 | del self.lines[-1] 76 | 77 | def execute(self, input): 78 | old_stderr, old_stdout = sys.stderr, sys.stdout 79 | sys.stderr = sys.stdout = self 80 | try: 81 | c = code.compile_command(input, '') 82 | if c is None: 83 | self.pre_buffer = '%s\n' % input 84 | else: 85 | self.pre_buffer = '' 86 | result = eval(c, self.globals, self.locals) 87 | if result is not None: 88 | self.write('%r\n' % result) 89 | except: 90 | traceback.print_exc() 91 | self.pre_buffer = '' 92 | sys.stderr = old_stderr 93 | sys.stdout = old_stdout 94 | 95 | def get_prompt(self): 96 | if self.pre_buffer: 97 | return self.prompt2 98 | return self.prompt 99 | 100 | __last = None 101 | def draw(self): 102 | pyglet.text.begin() 103 | glPushMatrix() 104 | glTranslatef(0, self.height, 0) 105 | for line in self.lines[::-1]: 106 | line.draw() 107 | glTranslatef(0, -self.font.glyph_height, 0) 108 | line = self.get_prompt() + self.buffer 109 | if self.__last is None or line != self.__last[0]: 110 | self.__last = (line, pyglet.text.layout_text(line.strip(), 111 | font=self.font)) 112 | self.__last[1].draw() 113 | glPopMatrix() 114 | 115 | pyglet.text.end() 116 | 117 | if __name__ == '__main__': 118 | from pyglet.window import * 119 | from pyglet.window.event import * 120 | from pyglet import clock 121 | w1 = Window(width=600, height=400) 122 | console = Console(w1.width, w1.height) 123 | 124 | w1.push_handlers(console) 125 | 126 | c = clock.Clock() 127 | 128 | glMatrixMode(GL_PROJECTION) 129 | glLoadIdentity() 130 | glOrtho(0, w1.width, 0, w1.height, -1, 1) 131 | glEnable(GL_COLOR_MATERIAL) 132 | 133 | glMatrixMode(GL_MODELVIEW) 134 | glClearColor(1, 1, 1, 1) 135 | while not w1.has_exit: 136 | c.set_fps(60) 137 | w1.dispatch_events() 138 | glClear(GL_COLOR_BUFFER_BIT) 139 | console.draw() 140 | w1.flip() 141 | 142 | -------------------------------------------------------------------------------- /tests/06nested/[result]/pyglet/font/win32.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # pyglet 3 | # Copyright (c) 2006-2008 Alex Holkner 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in 14 | # the documentation and/or other materials provided with the 15 | # distribution. 16 | # * Neither the name of pyglet nor the names of its 17 | # contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written 19 | # permission. 20 | # 21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ---------------------------------------------------------------------------- 34 | 35 | ''' 36 | ''' 37 | 38 | # TODO Windows Vista: need to call SetProcessDPIAware? May affect GDI+ calls 39 | # as well as font. 40 | 41 | from ctypes import * 42 | import ctypes 43 | import math 44 | 45 | from sys import byteorder 46 | import pyglet 47 | from pyglet.font import base 48 | from pyglet.font import win32query 49 | import pyglet.image 50 | from pyglet.libs.win32.constants import * 51 | from pyglet.libs.win32.types import * 52 | from pyglet.libs.win32 import _gdi32 as gdi32, _user32 as user32 53 | from pyglet.libs.win32 import _kernel32 as kernel32 54 | from pyglet.compat import asbytes 55 | 56 | _debug_font = pyglet.options['debug_font'] 57 | 58 | 59 | def str_ucs2(text): 60 | if byteorder == 'big': 61 | text = text.encode('utf_16_be') 62 | else: 63 | text = text.encode('utf_16_le') # explicit endian avoids BOM 64 | return create_string_buffer(text + '\0') 65 | 66 | _debug_dir = 'debug_font' 67 | def _debug_filename(base, extension): 68 | import os 69 | if not os.path.exists(_debug_dir): 70 | os.makedirs(_debug_dir) 71 | name = '%s-%%d.%%s' % os.path.join(_debug_dir, base) 72 | num = 1 73 | while os.path.exists(name % (num, extension)): 74 | num += 1 75 | return name % (num, extension) 76 | 77 | def _debug_image(image, name): 78 | filename = _debug_filename(name, 'png') 79 | image.save(filename) 80 | _debug('Saved image %r to %s' % (image, filename)) 81 | 82 | _debug_logfile = None 83 | def _debug(msg): 84 | global _debug_logfile 85 | if not _debug_logfile: 86 | _debug_logfile = open(_debug_filename('log', 'txt'), 'wt') 87 | _debug_logfile.write(msg + '\n') 88 | 89 | class Win32GlyphRenderer(base.GlyphRenderer): 90 | _bitmap = None 91 | _dc = None 92 | _bitmap_rect = None 93 | 94 | def __init__(self, font): 95 | super(Win32GlyphRenderer, self).__init__(font) 96 | self.font = font 97 | 98 | # Pessimistically round up width and height to 4 byte alignment 99 | width = font.max_glyph_width 100 | height = font.ascent - font.descent 101 | width = (width | 0x3) + 1 102 | height = (height | 0x3) + 1 103 | self._create_bitmap(width, height) 104 | 105 | gdi32.SelectObject(self._dc, self.font.hfont) 106 | 107 | def _create_bitmap(self, width, height): 108 | pass 109 | 110 | def render(self, text): 111 | raise NotImplementedError('abstract') 112 | 113 | class GDIGlyphRenderer(Win32GlyphRenderer): 114 | def __del__(self): 115 | try: 116 | if self._dc: 117 | gdi32.DeleteDC(self._dc) 118 | if self._bitmap: 119 | gdi32.DeleteObject(self._bitmap) 120 | except: 121 | pass 122 | 123 | def render(self, text): 124 | # Attempt to get ABC widths (only for TrueType) 125 | abc = ABC() 126 | if gdi32.GetCharABCWidthsW(self._dc, 127 | ord(text), ord(text), byref(abc)): 128 | width = abc.abcB 129 | lsb = abc.abcA 130 | advance = abc.abcA + abc.abcB + abc.abcC 131 | else: 132 | width_buf = c_int() 133 | gdi32.GetCharWidth32W(self._dc, 134 | ord(text), ord(text), byref(width_buf)) 135 | width = width_buf.value 136 | lsb = 0 137 | advance = width 138 | 139 | # Can't get glyph-specific dimensions, use whole line-height. 140 | height = self._bitmap_height 141 | image = self._get_image(text, width, height, lsb) 142 | 143 | glyph = self.font.create_glyph(image) 144 | glyph.set_bearings(-self.font.descent, lsb, advance) 145 | 146 | if _debug_font: 147 | _debug('%r.render(%s)' % (self, text)) 148 | _debug('abc.abcA = %r' % abc.abcA) 149 | _debug('abc.abcB = %r' % abc.abcB) 150 | _debug('abc.abcC = %r' % abc.abcC) 151 | _debug('width = %r' % width) 152 | _debug('height = %r' % height) 153 | _debug('lsb = %r' % lsb) 154 | _debug('advance = %r' % advance) 155 | _debug_image(image, 'glyph_%s' % text) 156 | _debug_image(self.font.textures[0], 'tex_%s' % text) 157 | 158 | return glyph 159 | 160 | def _get_image(self, text, width, height, lsb): 161 | # There's no such thing as a greyscale bitmap format in GDI. We can 162 | # create an 8-bit palette bitmap with 256 shades of grey, but 163 | # unfortunately antialiasing will not work on such a bitmap. So, we 164 | # use a 32-bit bitmap and use the red channel as OpenGL's alpha. 165 | 166 | gdi32.SelectObject(self._dc, self._bitmap) 167 | gdi32.SelectObject(self._dc, self.font.hfont) 168 | gdi32.SetBkColor(self._dc, 0x0) 169 | gdi32.SetTextColor(self._dc, 0x00ffffff) 170 | gdi32.SetBkMode(self._dc, OPAQUE) 171 | 172 | # Draw to DC 173 | user32.FillRect(self._dc, byref(self._bitmap_rect), self._black) 174 | gdi32.ExtTextOutA(self._dc, -lsb, 0, 0, None, text, 175 | len(text), None) 176 | gdi32.GdiFlush() 177 | 178 | # Create glyph object and copy bitmap data to texture 179 | image = pyglet.image.ImageData(width, height, 180 | 'AXXX', self._bitmap_data, self._bitmap_rect.right * 4) 181 | return image 182 | 183 | def _create_bitmap(self, width, height): 184 | self._black = gdi32.GetStockObject(BLACK_BRUSH) 185 | self._white = gdi32.GetStockObject(WHITE_BRUSH) 186 | 187 | if self._dc: 188 | gdi32.ReleaseDC(self._dc) 189 | if self._bitmap: 190 | gdi32.DeleteObject(self._bitmap) 191 | 192 | pitch = width * 4 193 | data = POINTER(c_byte * (height * pitch))() 194 | info = BITMAPINFO() 195 | info.bmiHeader.biSize = sizeof(info.bmiHeader) 196 | info.bmiHeader.biWidth = width 197 | info.bmiHeader.biHeight = height 198 | info.bmiHeader.biPlanes = 1 199 | info.bmiHeader.biBitCount = 32 200 | info.bmiHeader.biCompression = BI_RGB 201 | 202 | self._dc = gdi32.CreateCompatibleDC(None) 203 | self._bitmap = gdi32.CreateDIBSection(None, 204 | byref(info), DIB_RGB_COLORS, byref(data), None, 205 | 0) 206 | # Spookiness: the above line causes a "not enough storage" error, 207 | # even though that error cannot be generated according to docs, 208 | # and everything works fine anyway. Call SetLastError to clear it. 209 | kernel32.SetLastError(0) 210 | 211 | self._bitmap_data = data.contents 212 | self._bitmap_rect = RECT() 213 | self._bitmap_rect.left = 0 214 | self._bitmap_rect.right = width 215 | self._bitmap_rect.top = 0 216 | self._bitmap_rect.bottom = height 217 | self._bitmap_height = height 218 | 219 | if _debug_font: 220 | _debug('%r._create_dc(%d, %d)' % (self, width, height)) 221 | _debug('_dc = %r' % self._dc) 222 | _debug('_bitmap = %r' % self._bitmap) 223 | _debug('pitch = %r' % pitch) 224 | _debug('info.bmiHeader.biSize = %r' % info.bmiHeader.biSize) 225 | 226 | class Win32Font(base.Font): 227 | glyph_renderer_class = GDIGlyphRenderer 228 | 229 | def __init__(self, name, size, bold=False, italic=False, dpi=None): 230 | super(Win32Font, self).__init__() 231 | 232 | self.logfont = self.get_logfont(name, size, bold, italic, dpi) 233 | self.hfont = gdi32.CreateFontIndirectA(byref(self.logfont)) 234 | 235 | # Create a dummy DC for coordinate mapping 236 | dc = user32.GetDC(0) 237 | metrics = TEXTMETRIC() 238 | gdi32.SelectObject(dc, self.hfont) 239 | gdi32.GetTextMetricsA(dc, byref(metrics)) 240 | self.ascent = metrics.tmAscent 241 | self.descent = -metrics.tmDescent 242 | self.max_glyph_width = metrics.tmMaxCharWidth 243 | 244 | @staticmethod 245 | def get_logfont(name, size, bold, italic, dpi): 246 | # Create a dummy DC for coordinate mapping 247 | dc = user32.GetDC(0) 248 | if dpi is None: 249 | dpi = 96 250 | logpixelsy = dpi 251 | 252 | logfont = LOGFONT() 253 | # Conversion of point size to device pixels 254 | logfont.lfHeight = int(-size * logpixelsy // 72) 255 | if bold: 256 | logfont.lfWeight = FW_BOLD 257 | else: 258 | logfont.lfWeight = FW_NORMAL 259 | logfont.lfItalic = italic 260 | logfont.lfFaceName = asbytes(name) 261 | logfont.lfQuality = ANTIALIASED_QUALITY 262 | return logfont 263 | 264 | @classmethod 265 | def have_font(cls, name): 266 | return win32query.have_font(name) 267 | 268 | @classmethod 269 | def add_font_data(cls, data): 270 | numfonts = c_uint32() 271 | gdi32.AddFontMemResourceEx(data, len(data), 0, byref(numfonts)) 272 | 273 | # ... -------------------------------------------------------------------------------- /tests/06nested/[result]/tests/app/EVENT_LOOP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | '''Test that the event loop can do timing. 3 | 4 | The test will display a series of intervals, iterations and sleep times. 5 | It should then display an incrementing number up to 2x the number of 6 | iterations, at a rate determined by the interval. 7 | ''' 8 | 9 | import sys 10 | import unittest 11 | 12 | import pyglet 13 | 14 | __noninteractive = True 15 | 16 | if sys.platform in ('win32', 'cygwin'): 17 | from time import clock as time 18 | else: 19 | from time import time 20 | from time import sleep 21 | 22 | class EVENT_LOOP(unittest.TestCase): 23 | def t_scheduled(self, interval, iterations, sleep_time=0): 24 | print('Test interval=%s, iterations=%s, sleep=%s' % (interval, 25 | iterations, sleep_time)) 26 | warmup_iterations = iterations 27 | 28 | self.last_t = 0. 29 | self.timer_count = 0 30 | def f(dt): 31 | sys.stdout.write('%s\r' % self.timer_count) 32 | sys.stdout.flush() 33 | t = time() 34 | self.timer_count += 1 35 | tc = self.timer_count 36 | if tc > warmup_iterations: 37 | self.assertAlmostEqual(dt, interval, places=2) 38 | self.assertAlmostEqual(t - self.last_t, interval, places=2) 39 | self.last_t = t 40 | 41 | if self.timer_count > iterations + warmup_iterations: 42 | pyglet.app.exit() 43 | if sleep_time: 44 | sleep(sleep_time) 45 | 46 | pyglet.clock.schedule_interval(f, interval) 47 | try: 48 | pyglet.app.run() 49 | finally: 50 | pyglet.clock.unschedule(f) 51 | print 52 | 53 | def test_1_5(self): 54 | self.t_scheduled(1, 5, 0) 55 | 56 | def test_1_5_d5(self): 57 | self.t_scheduled(1, 5, 0.5) 58 | 59 | def test_d1_50(self): 60 | self.t_scheduled(.1, 50) 61 | 62 | def test_d1_50_d05(self): 63 | self.t_scheduled(.1, 50, 0.05) 64 | 65 | def test_d05_50(self): 66 | self.t_scheduled(.05, 50) 67 | 68 | def test_d05_50_d03(self): 69 | self.t_scheduled(.05, 50, 0.03) 70 | 71 | def test_d02_50(self): 72 | self.t_scheduled(.02, 50) 73 | 74 | def test_d01_50(self): 75 | self.t_scheduled(.01, 50) 76 | 77 | if __name__ == '__main__': 78 | if pyglet.version != '1.2dev': 79 | print('Wrong version of pyglet imported; please check your PYTHONPATH') 80 | else: 81 | unittest.main() 82 | -------------------------------------------------------------------------------- /tests/06nested/[result]/tests/font/SYSTEM.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | '''Test that a font likely to be installed on the computer can be 4 | loaded and displayed correctly. 5 | 6 | One window will open, it should show "Quickly brown fox" at 24pt using: 7 | 8 | * "Helvetica" on Mac OS X 9 | * "Arial" on Windows 10 | 11 | ''' 12 | 13 | __docformat__ = 'restructuredtext' 14 | __version__ = '$Id: $' 15 | 16 | import unittest 17 | import sys 18 | from . import base_text 19 | 20 | if sys.platform == 'darwin': 21 | font_name = 'Helvetica' 22 | elif sys.platform in ('win32', 'cygwin'): 23 | font_name = 'Modern No.20' 24 | else: 25 | font_name = 'Arial' 26 | 27 | class TEST_SYSTEM(base_text.TextTestBase): 28 | font_name = font_name 29 | font_size = 24 30 | 31 | if __name__ == '__main__': 32 | unittest.main() 33 | -------------------------------------------------------------------------------- /tests/06nested/examples/font_comparison.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # ---------------------------------------------------------------------------- 3 | # pyglet 4 | # Copyright (c) 2006-2008 Alex Holkner 5 | # All rights reserved. 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions 9 | # are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in 15 | # the documentation and/or other materials provided with the 16 | # distribution. 17 | # * Neither the name of pyglet nor the names of its 18 | # contributors may be used to endorse or promote products 19 | # derived from this software without specific prior written 20 | # permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | # ---------------------------------------------------------------------------- 35 | 36 | '''A simple tool that may be used to compare font faces. 37 | 38 | Use the left/right cursor keys to change font faces. 39 | ''' 40 | 41 | __docformat__ = 'restructuredtext' 42 | __version__ = '$Id: $' 43 | import pyglet 44 | 45 | FONTS = ['Andale Mono', 'Consolas', 'Inconsolata', 'Inconsolata-dz', 'Monaco', 46 | 'Menlo'] 47 | 48 | SAMPLE = '''class Spam(object): 49 | def __init__(self): 50 | # The quick brown fox 51 | self.spam = {"jumped": 'over'} 52 | @the 53 | def lazy(self, *dog): 54 | self.dog = [lazy, lazy]''' 55 | 56 | class Window(pyglet.window.Window): 57 | font_num = 0 58 | def on_text_motion(self, motion): 59 | if motion == pyglet.window.key.MOTION_RIGHT: 60 | self.font_num += 1 61 | if self.font_num == len(FONTS): 62 | self.font_num = 0 63 | elif motion == pyglet.window.key.MOTION_LEFT: 64 | self.font_num -= 1 65 | if self.font_num < 0: 66 | self.font_num = len(FONTS) - 1 67 | 68 | face = FONTS[self.font_num] 69 | self.head = pyglet.text.Label(face, font_size=24, y=0, 70 | anchor_y='bottom') 71 | self.text = pyglet.text.Label(SAMPLE, font_name=face, font_size=18, 72 | y=self.height, anchor_y='top', width=self.width, multiline=True) 73 | 74 | def on_draw(self): 75 | self.clear() 76 | self.head.draw() 77 | self.text.draw() 78 | 79 | window = Window() 80 | window.on_text_motion(None) 81 | pyglet.app.run() 82 | -------------------------------------------------------------------------------- /tests/06nested/experimental/console.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | ''' 5 | 6 | __docformat__ = 'restructuredtext' 7 | __version__ = '$Id$' 8 | 9 | import code 10 | import sys 11 | import traceback 12 | 13 | import pyglet.event 14 | import pyglet.text 15 | from pyglet.window import key 16 | 17 | from pyglet.gl import * 18 | 19 | class Console(object): 20 | def __init__(self, width, height, globals=None, locals=None): 21 | self.font = pyglet.text.default_font_factory.get_font('bitstream vera sans mono', 12) 22 | self.lines = [] 23 | self.buffer = '' 24 | self.pre_buffer = '' 25 | self.prompt = '>>> ' 26 | self.prompt2 = '... ' 27 | self.globals = globals 28 | self.locals = locals 29 | self.write_pending = '' 30 | 31 | self.width, self.height = (width, height) 32 | self.max_lines = self.height / self.font.glyph_height - 1 33 | 34 | self.write('pyglet command console\n') 35 | self.write('Version %s\n' % __version__) 36 | 37 | def on_key_press(self, symbol, modifiers): 38 | # TODO cursor control / line editing 39 | if modifiers & key.key.MOD_CTRL and symbol == key.key.C: 40 | self.buffer = '' 41 | self.pre_buffer = '' 42 | return 43 | if symbol == key.key.ENTER: 44 | self.write('%s%s\n' % (self.get_prompt(), self.buffer)) 45 | self.execute(self.pre_buffer + self.buffer) 46 | self.buffer = '' 47 | return 48 | if symbol == key.key.BACKSPACE: 49 | self.buffer = self.buffer[:-1] 50 | return 51 | return EVENT_UNHANDLED 52 | 53 | def on_text(self, text): 54 | if ' ' <= text <= '~': 55 | self.buffer += text 56 | if 0xae <= ord(text) <= 0xff: 57 | self.buffer += text 58 | 59 | def write(self, text): 60 | if self.write_pending: 61 | text = self.write_pending + text 62 | self.write_pending = '' 63 | 64 | if type(text) in (str, unicode): 65 | text = text.split('\n') 66 | 67 | if text[-1] != '': 68 | self.write_pending = text[-1] 69 | del text[-1] 70 | 71 | self.lines = [pyglet.text.layout_text(line.strip(), font=self.font) 72 | for line in text] + self.lines 73 | 74 | if len(self.lines) > self.max_lines: 75 | del self.lines[-1] 76 | 77 | def execute(self, input): 78 | old_stderr, old_stdout = sys.stderr, sys.stdout 79 | sys.stderr = sys.stdout = self 80 | try: 81 | c = code.compile_command(input, '') 82 | if c is None: 83 | self.pre_buffer = '%s\n' % input 84 | else: 85 | self.pre_buffer = '' 86 | result = eval(c, self.globals, self.locals) 87 | if result is not None: 88 | self.write('%r\n' % result) 89 | except: 90 | traceback.print_exc() 91 | self.pre_buffer = '' 92 | sys.stderr = old_stderr 93 | sys.stdout = old_stdout 94 | 95 | def get_prompt(self): 96 | if self.pre_buffer: 97 | return self.prompt2 98 | return self.prompt 99 | 100 | __last = None 101 | def draw(self): 102 | pyglet.text.begin() 103 | glPushMatrix() 104 | glTranslatef(0, self.height, 0) 105 | for line in self.lines[::-1]: 106 | line.draw() 107 | glTranslatef(0, -self.font.glyph_height, 0) 108 | line = self.get_prompt() + self.buffer 109 | if self.__last is None or line != self.__last[0]: 110 | self.__last = (line, pyglet.text.layout_text(line.strip(), 111 | font=self.font)) 112 | self.__last[1].draw() 113 | glPopMatrix() 114 | 115 | pyglet.text.end() 116 | 117 | if __name__ == '__main__': 118 | from pyglet.window import * 119 | from pyglet.window.event import * 120 | from pyglet import clock 121 | w1 = Window(width=600, height=400) 122 | console = Console(w1.width, w1.height) 123 | 124 | w1.push_handlers(console) 125 | 126 | c = clock.Clock() 127 | 128 | glMatrixMode(GL_PROJECTION) 129 | glLoadIdentity() 130 | glOrtho(0, w1.width, 0, w1.height, -1, 1) 131 | glEnable(GL_COLOR_MATERIAL) 132 | 133 | glMatrixMode(GL_MODELVIEW) 134 | glClearColor(1, 1, 1, 1) 135 | while not w1.has_exit: 136 | c.set_fps(60) 137 | w1.dispatch_events() 138 | glClear(GL_COLOR_BUFFER_BIT) 139 | console.draw() 140 | w1.flip() 141 | 142 | -------------------------------------------------------------------------------- /tests/06nested/pyglet/font/win32.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # pyglet 3 | # Copyright (c) 2006-2008 Alex Holkner 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in 14 | # the documentation and/or other materials provided with the 15 | # distribution. 16 | # * Neither the name of pyglet nor the names of its 17 | # contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written 19 | # permission. 20 | # 21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ---------------------------------------------------------------------------- 34 | 35 | ''' 36 | ''' 37 | 38 | # TODO Windows Vista: need to call SetProcessDPIAware? May affect GDI+ calls 39 | # as well as font. 40 | 41 | from ctypes import * 42 | import ctypes 43 | import math 44 | 45 | from sys import byteorder 46 | import pyglet 47 | from pyglet.font import base 48 | import pyglet.image 49 | from pyglet.libs.win32.constants import * 50 | from pyglet.libs.win32.types import * 51 | from pyglet.libs.win32 import _gdi32 as gdi32, _user32 as user32 52 | from pyglet.libs.win32 import _kernel32 as kernel32 53 | from pyglet.compat import asbytes 54 | 55 | _debug_font = pyglet.options['debug_font'] 56 | 57 | 58 | def str_ucs2(text): 59 | if byteorder == 'big': 60 | text = text.encode('utf_16_be') 61 | else: 62 | text = text.encode('utf_16_le') # explicit endian avoids BOM 63 | return create_string_buffer(text + '\0') 64 | 65 | _debug_dir = 'debug_font' 66 | def _debug_filename(base, extension): 67 | import os 68 | if not os.path.exists(_debug_dir): 69 | os.makedirs(_debug_dir) 70 | name = '%s-%%d.%%s' % os.path.join(_debug_dir, base) 71 | num = 1 72 | while os.path.exists(name % (num, extension)): 73 | num += 1 74 | return name % (num, extension) 75 | 76 | def _debug_image(image, name): 77 | filename = _debug_filename(name, 'png') 78 | image.save(filename) 79 | _debug('Saved image %r to %s' % (image, filename)) 80 | 81 | _debug_logfile = None 82 | def _debug(msg): 83 | global _debug_logfile 84 | if not _debug_logfile: 85 | _debug_logfile = open(_debug_filename('log', 'txt'), 'wt') 86 | _debug_logfile.write(msg + '\n') 87 | 88 | class Win32GlyphRenderer(base.GlyphRenderer): 89 | _bitmap = None 90 | _dc = None 91 | _bitmap_rect = None 92 | 93 | def __init__(self, font): 94 | super(Win32GlyphRenderer, self).__init__(font) 95 | self.font = font 96 | 97 | # Pessimistically round up width and height to 4 byte alignment 98 | width = font.max_glyph_width 99 | height = font.ascent - font.descent 100 | width = (width | 0x3) + 1 101 | height = (height | 0x3) + 1 102 | self._create_bitmap(width, height) 103 | 104 | gdi32.SelectObject(self._dc, self.font.hfont) 105 | 106 | def _create_bitmap(self, width, height): 107 | pass 108 | 109 | def render(self, text): 110 | raise NotImplementedError('abstract') 111 | 112 | class GDIGlyphRenderer(Win32GlyphRenderer): 113 | def __del__(self): 114 | try: 115 | if self._dc: 116 | gdi32.DeleteDC(self._dc) 117 | if self._bitmap: 118 | gdi32.DeleteObject(self._bitmap) 119 | except: 120 | pass 121 | 122 | def render(self, text): 123 | # Attempt to get ABC widths (only for TrueType) 124 | abc = ABC() 125 | if gdi32.GetCharABCWidthsW(self._dc, 126 | ord(text), ord(text), byref(abc)): 127 | width = abc.abcB 128 | lsb = abc.abcA 129 | advance = abc.abcA + abc.abcB + abc.abcC 130 | else: 131 | width_buf = c_int() 132 | gdi32.GetCharWidth32W(self._dc, 133 | ord(text), ord(text), byref(width_buf)) 134 | width = width_buf.value 135 | lsb = 0 136 | advance = width 137 | 138 | # Can't get glyph-specific dimensions, use whole line-height. 139 | height = self._bitmap_height 140 | image = self._get_image(text, width, height, lsb) 141 | 142 | glyph = self.font.create_glyph(image) 143 | glyph.set_bearings(-self.font.descent, lsb, advance) 144 | 145 | if _debug_font: 146 | _debug('%r.render(%s)' % (self, text)) 147 | _debug('abc.abcA = %r' % abc.abcA) 148 | _debug('abc.abcB = %r' % abc.abcB) 149 | _debug('abc.abcC = %r' % abc.abcC) 150 | _debug('width = %r' % width) 151 | _debug('height = %r' % height) 152 | _debug('lsb = %r' % lsb) 153 | _debug('advance = %r' % advance) 154 | _debug_image(image, 'glyph_%s' % text) 155 | _debug_image(self.font.textures[0], 'tex_%s' % text) 156 | 157 | return glyph 158 | 159 | def _get_image(self, text, width, height, lsb): 160 | # There's no such thing as a greyscale bitmap format in GDI. We can 161 | # create an 8-bit palette bitmap with 256 shades of grey, but 162 | # unfortunately antialiasing will not work on such a bitmap. So, we 163 | # use a 32-bit bitmap and use the red channel as OpenGL's alpha. 164 | 165 | gdi32.SelectObject(self._dc, self._bitmap) 166 | gdi32.SelectObject(self._dc, self.font.hfont) 167 | gdi32.SetBkColor(self._dc, 0x0) 168 | gdi32.SetTextColor(self._dc, 0x00ffffff) 169 | gdi32.SetBkMode(self._dc, OPAQUE) 170 | 171 | # Draw to DC 172 | user32.FillRect(self._dc, byref(self._bitmap_rect), self._black) 173 | gdi32.ExtTextOutA(self._dc, -lsb, 0, 0, None, text, 174 | len(text), None) 175 | gdi32.GdiFlush() 176 | 177 | # Create glyph object and copy bitmap data to texture 178 | image = pyglet.image.ImageData(width, height, 179 | 'AXXX', self._bitmap_data, self._bitmap_rect.right * 4) 180 | return image 181 | 182 | def _create_bitmap(self, width, height): 183 | self._black = gdi32.GetStockObject(BLACK_BRUSH) 184 | self._white = gdi32.GetStockObject(WHITE_BRUSH) 185 | 186 | if self._dc: 187 | gdi32.ReleaseDC(self._dc) 188 | if self._bitmap: 189 | gdi32.DeleteObject(self._bitmap) 190 | 191 | pitch = width * 4 192 | data = POINTER(c_byte * (height * pitch))() 193 | info = BITMAPINFO() 194 | info.bmiHeader.biSize = sizeof(info.bmiHeader) 195 | info.bmiHeader.biWidth = width 196 | info.bmiHeader.biHeight = height 197 | info.bmiHeader.biPlanes = 1 198 | info.bmiHeader.biBitCount = 32 199 | info.bmiHeader.biCompression = BI_RGB 200 | 201 | self._dc = gdi32.CreateCompatibleDC(None) 202 | self._bitmap = gdi32.CreateDIBSection(None, 203 | byref(info), DIB_RGB_COLORS, byref(data), None, 204 | 0) 205 | # Spookiness: the above line causes a "not enough storage" error, 206 | # even though that error cannot be generated according to docs, 207 | # and everything works fine anyway. Call SetLastError to clear it. 208 | kernel32.SetLastError(0) 209 | 210 | self._bitmap_data = data.contents 211 | self._bitmap_rect = RECT() 212 | self._bitmap_rect.left = 0 213 | self._bitmap_rect.right = width 214 | self._bitmap_rect.top = 0 215 | self._bitmap_rect.bottom = height 216 | self._bitmap_height = height 217 | 218 | if _debug_font: 219 | _debug('%r._create_dc(%d, %d)' % (self, width, height)) 220 | _debug('_dc = %r' % self._dc) 221 | _debug('_bitmap = %r' % self._bitmap) 222 | _debug('pitch = %r' % pitch) 223 | _debug('info.bmiHeader.biSize = %r' % info.bmiHeader.biSize) 224 | 225 | class Win32Font(base.Font): 226 | glyph_renderer_class = GDIGlyphRenderer 227 | 228 | def __init__(self, name, size, bold=False, italic=False, dpi=None): 229 | super(Win32Font, self).__init__() 230 | 231 | self.logfont = self.get_logfont(name, size, bold, italic, dpi) 232 | self.hfont = gdi32.CreateFontIndirectA(byref(self.logfont)) 233 | 234 | # Create a dummy DC for coordinate mapping 235 | dc = user32.GetDC(0) 236 | metrics = TEXTMETRIC() 237 | gdi32.SelectObject(dc, self.hfont) 238 | gdi32.GetTextMetricsA(dc, byref(metrics)) 239 | self.ascent = metrics.tmAscent 240 | self.descent = -metrics.tmDescent 241 | self.max_glyph_width = metrics.tmMaxCharWidth 242 | 243 | @staticmethod 244 | def get_logfont(name, size, bold, italic, dpi): 245 | # Create a dummy DC for coordinate mapping 246 | dc = user32.GetDC(0) 247 | if dpi is None: 248 | dpi = 96 249 | logpixelsy = dpi 250 | 251 | logfont = LOGFONT() 252 | # Conversion of point size to device pixels 253 | logfont.lfHeight = int(-size * logpixelsy // 72) 254 | if bold: 255 | logfont.lfWeight = FW_BOLD 256 | else: 257 | logfont.lfWeight = FW_NORMAL 258 | logfont.lfItalic = italic 259 | logfont.lfFaceName = asbytes(name) 260 | logfont.lfQuality = ANTIALIASED_QUALITY 261 | return logfont 262 | 263 | @classmethod 264 | def have_font(cls, name): 265 | # CreateFontIndirect always returns a font... have to work out 266 | # something with EnumFontFamily... TODO 267 | return True 268 | 269 | @classmethod 270 | def add_font_data(cls, data): 271 | numfonts = c_uint32() 272 | gdi32.AddFontMemResourceEx(data, len(data), 0, byref(numfonts)) 273 | 274 | # ... -------------------------------------------------------------------------------- /tests/06nested/tests/app/EVENT_LOOP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | '''Test that the event loop can do timing. 3 | 4 | The test will display a series of intervals, iterations and sleep times. 5 | It should then display an incrementing number up to 2x the number of 6 | iterations, at a rate determined by the interval. 7 | ''' 8 | 9 | import sys 10 | import unittest 11 | 12 | import pyglet 13 | 14 | __noninteractive = True 15 | 16 | if sys.platform in ('win32', 'cygwin'): 17 | from time import clock as time 18 | else: 19 | from time import time 20 | from time import sleep 21 | 22 | class EVENT_LOOP(unittest.TestCase): 23 | def t_scheduled(self, interval, iterations, sleep_time=0): 24 | print 'Test interval=%s, iterations=%s, sleep=%s' % (interval, 25 | iterations, sleep_time) 26 | warmup_iterations = iterations 27 | 28 | self.last_t = 0. 29 | self.timer_count = 0 30 | def f(dt): 31 | sys.stdout.write('%s\r' % self.timer_count) 32 | sys.stdout.flush() 33 | t = time() 34 | self.timer_count += 1 35 | tc = self.timer_count 36 | if tc > warmup_iterations: 37 | self.assertAlmostEqual(dt, interval, places=2) 38 | self.assertAlmostEqual(t - self.last_t, interval, places=2) 39 | self.last_t = t 40 | 41 | if self.timer_count > iterations + warmup_iterations: 42 | pyglet.app.exit() 43 | if sleep_time: 44 | sleep(sleep_time) 45 | 46 | pyglet.clock.schedule_interval(f, interval) 47 | try: 48 | pyglet.app.run() 49 | finally: 50 | pyglet.clock.unschedule(f) 51 | print 52 | 53 | def test_1_5(self): 54 | self.t_scheduled(1, 5, 0) 55 | 56 | def test_1_5_d5(self): 57 | self.t_scheduled(1, 5, 0.5) 58 | 59 | def test_d1_50(self): 60 | self.t_scheduled(.1, 50) 61 | 62 | def test_d1_50_d05(self): 63 | self.t_scheduled(.1, 50, 0.05) 64 | 65 | def test_d05_50(self): 66 | self.t_scheduled(.05, 50) 67 | 68 | def test_d05_50_d03(self): 69 | self.t_scheduled(.05, 50, 0.03) 70 | 71 | def test_d02_50(self): 72 | self.t_scheduled(.02, 50) 73 | 74 | def test_d01_50(self): 75 | self.t_scheduled(.01, 50) 76 | 77 | if __name__ == '__main__': 78 | if pyglet.version != '1.2dev': 79 | print 'Wrong version of pyglet imported; please check your PYTHONPATH' 80 | else: 81 | unittest.main() 82 | -------------------------------------------------------------------------------- /tests/06nested/tests/font/SYSTEM.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | '''Test that a font likely to be installed on the computer can be 4 | loaded and displayed correctly. 5 | 6 | One window will open, it should show "Quickly brown fox" at 24pt using: 7 | 8 | * "Helvetica" on Mac OS X 9 | * "Arial" on Windows 10 | 11 | ''' 12 | 13 | __docformat__ = 'restructuredtext' 14 | __version__ = '$Id: $' 15 | 16 | import unittest 17 | import sys 18 | from . import base_text 19 | 20 | if sys.platform == 'darwin': 21 | font_name = 'Helvetica' 22 | elif sys.platform in ('win32', 'cygwin'): 23 | font_name = 'Arial' 24 | else: 25 | font_name = 'Arial' 26 | 27 | class TEST_SYSTEM(base_text.TextTestBase): 28 | font_name = font_name 29 | font_size = 24 30 | 31 | if __name__ == '__main__': 32 | unittest.main() 33 | -------------------------------------------------------------------------------- /tests/07google_code_wiki.from: -------------------------------------------------------------------------------- 1 | #summary Plugin development 2 | #sidebar TOC 3 | 4 | 5 | !!WARNING!! The table below displays the navigation bar 6 | !!WARNING!! Please don't touch it unless you know what you're doing. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
http://wiki.spyderlib.googlecode.com/hg/Buttons/logo-mini.png
[http://code.google.com/p/spyderlib http://wiki.spyderlib.googlecode.com/hg/Buttons/home.png][http://code.google.com/p/spyderlib/downloads/list http://wiki.spyderlib.googlecode.com/hg/Buttons/download.png][http://code.google.com/p/spyderlib/wiki/Features http://wiki.spyderlib.googlecode.com/hg/Buttons/features.png][http://code.google.com/p/spyderlib/wiki/Screenshots http://wiki.spyderlib.googlecode.com/hg/Buttons/screenshots.png][http://packages.python.org/spyder/ http://wiki.spyderlib.googlecode.com/hg/Buttons/docs.png][http://code.google.com/p/spyderlib/wiki/Roadmap http://wiki.spyderlib.googlecode.com/hg/Buttons/roadmap.png][http://code.google.com/p/spyderlib/wiki/Support http://wiki.spyderlib.googlecode.com/hg/Buttons/support.png][http://code.google.com/p/spyderlib/wiki/Development http://wiki.spyderlib.googlecode.com/hg/Buttons/development_sel.png][http://spyder-ide.blogspot.com http://wiki.spyderlib.googlecode.com/hg/Buttons/blog.png]
25 | 26 | = Spyder plugin development = 27 | ---- 28 | 29 | == Introduction == 30 | 31 | Spyder plugins are just importable Python files that use internal APIs to do different stuff in IDE. Spyder goal is to be reusable set of components and widgets, so a lot of stuff inside is made with plugins. For example, code editor and python console are plugins. Object inspector, history browser and so on. You can see them [https://code.google.com/p/spyderlib/source/browse/#hg%2Fspyderlib%2Fplugins here]. 32 | 33 | There are two type of plugins in Spyder: 34 | * `core plugins` - these are standard Spyder components located in `spyderlib.plugins` module. They are explicitly imported like usual Python modules. 35 | * `external plugins` - are discovered and imported dynamically, so they should follow naming convention so that Spyder can find them. External plugins are also initialized later all at once when some of the `core` set are already loaded `[reference need]`. 36 | 37 | Both plugin types should include class that inherits from the same base plugin class. 38 | 39 | 40 | [ ] check if plugins can be enabled/disabled 41 | [ ] list of all lookup dirs 42 | [ ] how to debug plugin discovery 43 | [ ] API reference, version information 44 | [ ] time when core plugins are initialized 45 | [ ] time when external plugins are initialized 46 | 47 | == Architecture == 48 | 49 | Spyder main window is de-facto storage for all Spyder state. 50 | 51 | 52 | 53 | 54 | == Plugin discovery == 55 | 56 | `[more research is needed - ask techtonik@gmail.com if you feel capable]` 57 | 58 | While many software use directories to distribute and find plugins, Spyder uses Python modules. It's own plugin are located under `spyderlib` namespace with all Spyder application. It can be used as a library. External plugins are expected to be found in a special importable module named. 59 | 60 | * _spyderplugins_: this is what this page is about 61 | 62 | The module _spyderplugins_ includes third-party plugins of several kinds. 63 | TODO: check if prefix for plugin kind really affects loading or if it just a convention 64 | 65 | In Spyder v1.1, the only kind of plugin supported were the input/output plugins (modules starting with `io_`) which provide I/O functions for the variable explorer (_Workspace_ and _Globals explorer_ in v1.1, _Variable explorer_ in v2.0). Spyder natively supports .mat, .npy, .txt, .csv, .jpg, .png and .tiff file formats. These plugins allow users to add their own types, like HDF5 files for example. 66 | 67 | In Spyder v2.0, any kind of plugin may be created in the module _spyderplugins_. These third-party plugins may communicate with other Spyder components through the plugin interface (see [http://code.google.com/p/spyderlib/source/browse/spyderlib/plugins/__init__.py spyderlib/plugins/__init__.py]). 68 | 69 | == I/O Spyder plugins == 70 | 71 | How to create your own I/O Spyder plugins: 72 | * Create a Python module named `io_foobar.py` where `foobar` is the name of your plugin 73 | * Write your loading function and/or your saving function: 74 | * load: 75 | * input is a string: filename 76 | * output is a tuple with two elements: dictionary containing at least one element (key is the variable name) and error message (or `None`) 77 | * save: 78 | * input is a tuple with two elements: data (dictionary), filename 79 | * output is a string or `None`: error message or `None` if no error occured 80 | * Define the global variables `FORMAT_NAME`, `FORMAT_EXT`, `FORMAT_LOAD` and `FORMAT_SAVE`. See the example of DICOM images support: 81 | http://code.google.com/p/spyderlib/source/browse/spyderplugins/io_dicom.py 82 | * More examples of load/save functions may be found here: 83 | http://code.google.com/p/spyderlib/source/browse/spyderlib/utils/iofuncs.py 84 | 85 | == Other Spyder plugins == 86 | 87 | See the example of the `pylint` third-party plugin in Spyder v2.0. 88 | -------------------------------------------------------------------------------- /tests/07google_code_wiki.patch: -------------------------------------------------------------------------------- 1 | --- a/07google_code_wiki.from 2 | +++ b/07google_code_wiki.to 3 | @@ -28,7 +28,7 @@ 4 | 5 | == Introduction == 6 | 7 | -Spyder plugins are just importable Python files that use internal APIs to do different stuff in IDE. Spyder goal is to be reusable set of components and widgets, so a lot of stuff inside is made with plugins. For example, code editor and python console are plugins. Object inspector, history browser and so on. You can see them [https://code.google.com/p/spyderlib/source/browse/#hg%2Fspyderlib%2Fplugins here]. 8 | +Spyder plugins are importable Python modules that may use internal API to do different stuff in IDE. Spyder goal is to be reusable set of components and widgets, so a lot of things is made with plugins. For example, code editor and python console are plugins. Object inspector, history browser and so on. You can see them [https://code.google.com/p/spyderlib/source/browse/#hg%2Fspyderlib%2Fplugins here]. 9 | 10 | There are two type of plugins in Spyder: 11 | * `core plugins` - these are standard Spyder components located in `spyderlib.plugins` module. They are explicitly imported like usual Python modules. 12 | @@ -55,15 +55,15 @@ 13 | 14 | `[more research is needed - ask techtonik@gmail.com if you feel capable]` 15 | 16 | -While many software use directories to distribute and find plugins, Spyder uses Python modules. It's own plugin are located under `spyderlib` namespace with all Spyder application. It can be used as a library. External plugins are expected to be found in a special importable module named. 17 | +Some software use directories for plugin installation and discovery. Spyder uses Python modules. Internal components are contained in `spyderlib` namespace and are imported explicitly. External and 3rd party plugins are automatically imported from certain predefined locations. One such location is `spyderplugins` module. 18 | 19 | * _spyderplugins_: this is what this page is about 20 | 21 | +To make you module appear in `spyderplugins` namespace, you need to make Spyder discover and import it first. In Spyder source code tree you can just drop your module into `spyderplugins` directory. This is more like hack than convenient plugin discovery interface, so proposal to fix this are welcome. One of the ideas is to inspect Python files without importing them with `astdump` modules and check if their interface is compatible. This will also allow Spyder to enable/disable plugins from config menu. 22 | + 23 | The module _spyderplugins_ includes third-party plugins of several kinds. 24 | TODO: check if prefix for plugin kind really affects loading or if it just a convention 25 | 26 | -In Spyder v1.1, the only kind of plugin supported were the input/output plugins (modules starting with `io_`) which provide I/O functions for the variable explorer (_Workspace_ and _Globals explorer_ in v1.1, _Variable explorer_ in v2.0). Spyder natively supports .mat, .npy, .txt, .csv, .jpg, .png and .tiff file formats. These plugins allow users to add their own types, like HDF5 files for example. 27 | - 28 | In Spyder v2.0, any kind of plugin may be created in the module _spyderplugins_. These third-party plugins may communicate with other Spyder components through the plugin interface (see [http://code.google.com/p/spyderlib/source/browse/spyderlib/plugins/__init__.py spyderlib/plugins/__init__.py]). 29 | 30 | == I/O Spyder plugins == 31 | @@ -82,6 +82,10 @@ 32 | * More examples of load/save functions may be found here: 33 | http://code.google.com/p/spyderlib/source/browse/spyderlib/utils/iofuncs.py 34 | 35 | +== History === 36 | + 37 | +Spyder v1.1 supported only input/output plugins (modules starting with `io_`) which provided I/O functions for the variable explorer (_Workspace_ and _Globals explorer_ in v1.1, _Variable explorer_ in v2.0). Spyder natively supports .mat, .npy, .txt, .csv, .jpg, .png and .tiff file formats out of the box. These input plugins allowed users to add their own types, like HDF5 files for example. 38 | + 39 | == Other Spyder plugins == 40 | 41 | See the example of the `pylint` third-party plugin in Spyder v2.0. 42 | -------------------------------------------------------------------------------- /tests/07google_code_wiki.to: -------------------------------------------------------------------------------- 1 | #summary Plugin development 2 | #sidebar TOC 3 | 4 | 5 | !!WARNING!! The table below displays the navigation bar 6 | !!WARNING!! Please don't touch it unless you know what you're doing. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
http://wiki.spyderlib.googlecode.com/hg/Buttons/logo-mini.png
[http://code.google.com/p/spyderlib http://wiki.spyderlib.googlecode.com/hg/Buttons/home.png][http://code.google.com/p/spyderlib/downloads/list http://wiki.spyderlib.googlecode.com/hg/Buttons/download.png][http://code.google.com/p/spyderlib/wiki/Features http://wiki.spyderlib.googlecode.com/hg/Buttons/features.png][http://code.google.com/p/spyderlib/wiki/Screenshots http://wiki.spyderlib.googlecode.com/hg/Buttons/screenshots.png][http://packages.python.org/spyder/ http://wiki.spyderlib.googlecode.com/hg/Buttons/docs.png][http://code.google.com/p/spyderlib/wiki/Roadmap http://wiki.spyderlib.googlecode.com/hg/Buttons/roadmap.png][http://code.google.com/p/spyderlib/wiki/Support http://wiki.spyderlib.googlecode.com/hg/Buttons/support.png][http://code.google.com/p/spyderlib/wiki/Development http://wiki.spyderlib.googlecode.com/hg/Buttons/development_sel.png][http://spyder-ide.blogspot.com http://wiki.spyderlib.googlecode.com/hg/Buttons/blog.png]
25 | 26 | = Spyder plugin development = 27 | ---- 28 | 29 | == Introduction == 30 | 31 | Spyder plugins are importable Python modules that may use internal API to do different stuff in IDE. Spyder goal is to be reusable set of components and widgets, so a lot of things is made with plugins. For example, code editor and python console are plugins. Object inspector, history browser and so on. You can see them [https://code.google.com/p/spyderlib/source/browse/#hg%2Fspyderlib%2Fplugins here]. 32 | 33 | There are two type of plugins in Spyder: 34 | * `core plugins` - these are standard Spyder components located in `spyderlib.plugins` module. They are explicitly imported like usual Python modules. 35 | * `external plugins` - are discovered and imported dynamically, so they should follow naming convention so that Spyder can find them. External plugins are also initialized later all at once when some of the `core` set are already loaded `[reference need]`. 36 | 37 | Both plugin types should include class that inherits from the same base plugin class. 38 | 39 | 40 | [ ] check if plugins can be enabled/disabled 41 | [ ] list of all lookup dirs 42 | [ ] how to debug plugin discovery 43 | [ ] API reference, version information 44 | [ ] time when core plugins are initialized 45 | [ ] time when external plugins are initialized 46 | 47 | == Architecture == 48 | 49 | Spyder main window is de-facto storage for all Spyder state. 50 | 51 | 52 | 53 | 54 | == Plugin discovery == 55 | 56 | `[more research is needed - ask techtonik@gmail.com if you feel capable]` 57 | 58 | Some software use directories for plugin installation and discovery. Spyder uses Python modules. Internal components are contained in `spyderlib` namespace and are imported explicitly. External and 3rd party plugins are automatically imported from certain predefined locations. One such location is `spyderplugins` module. 59 | 60 | * _spyderplugins_: this is what this page is about 61 | 62 | To make you module appear in `spyderplugins` namespace, you need to make Spyder discover and import it first. In Spyder source code tree you can just drop your module into `spyderplugins` directory. This is more like hack than convenient plugin discovery interface, so proposal to fix this are welcome. One of the ideas is to inspect Python files without importing them with `astdump` modules and check if their interface is compatible. This will also allow Spyder to enable/disable plugins from config menu. 63 | 64 | The module _spyderplugins_ includes third-party plugins of several kinds. 65 | TODO: check if prefix for plugin kind really affects loading or if it just a convention 66 | 67 | In Spyder v2.0, any kind of plugin may be created in the module _spyderplugins_. These third-party plugins may communicate with other Spyder components through the plugin interface (see [http://code.google.com/p/spyderlib/source/browse/spyderlib/plugins/__init__.py spyderlib/plugins/__init__.py]). 68 | 69 | == I/O Spyder plugins == 70 | 71 | How to create your own I/O Spyder plugins: 72 | * Create a Python module named `io_foobar.py` where `foobar` is the name of your plugin 73 | * Write your loading function and/or your saving function: 74 | * load: 75 | * input is a string: filename 76 | * output is a tuple with two elements: dictionary containing at least one element (key is the variable name) and error message (or `None`) 77 | * save: 78 | * input is a tuple with two elements: data (dictionary), filename 79 | * output is a string or `None`: error message or `None` if no error occured 80 | * Define the global variables `FORMAT_NAME`, `FORMAT_EXT`, `FORMAT_LOAD` and `FORMAT_SAVE`. See the example of DICOM images support: 81 | http://code.google.com/p/spyderlib/source/browse/spyderplugins/io_dicom.py 82 | * More examples of load/save functions may be found here: 83 | http://code.google.com/p/spyderlib/source/browse/spyderlib/utils/iofuncs.py 84 | 85 | == History === 86 | 87 | Spyder v1.1 supported only input/output plugins (modules starting with `io_`) which provided I/O functions for the variable explorer (_Workspace_ and _Globals explorer_ in v1.1, _Variable explorer_ in v2.0). Spyder natively supports .mat, .npy, .txt, .csv, .jpg, .png and .tiff file formats out of the box. These input plugins allowed users to add their own types, like HDF5 files for example. 88 | 89 | == Other Spyder plugins == 90 | 91 | See the example of the `pylint` third-party plugin in Spyder v2.0. 92 | -------------------------------------------------------------------------------- /tests/08create/08create.patch: -------------------------------------------------------------------------------- 1 | diff -urN from/created to/created 2 | --- created 1970-01-01 01:00:00.000000000 +0100 3 | +++ created 2016-06-07 00:43:08.701304500 +0100 4 | @@ -0,0 +1 @@ 5 | +Created by patch 6 | -------------------------------------------------------------------------------- /tests/08create/[result]/created: -------------------------------------------------------------------------------- 1 | Created by patch 2 | -------------------------------------------------------------------------------- /tests/09delete/09delete.patch: -------------------------------------------------------------------------------- 1 | diff -urN from/deleted to/deleted 2 | --- deleted 2016-06-07 00:44:19.093323300 +0100 3 | +++ deleted 1970-01-01 01:00:00.000000000 +0100 4 | @@ -1 +0,0 @@ 5 | -Deleted by patch 6 | -------------------------------------------------------------------------------- /tests/09delete/[result]/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/09delete/deleted: -------------------------------------------------------------------------------- 1 | Deleted by patch 2 | -------------------------------------------------------------------------------- /tests/10fuzzy/10fuzzy.patch: -------------------------------------------------------------------------------- 1 | diff --git a/Jamroot b/Jamroot 2 | index a6981dd..0c08f09 100644 3 | --- a/Jamroot 4 | +++ b/Jamroot 5 | @@ -1,3 +1,4 @@ 6 | X 7 | YYYY 8 | +V 9 | W -------------------------------------------------------------------------------- /tests/10fuzzy/Jamroot: -------------------------------------------------------------------------------- 1 | X 2 | Y 3 | Z -------------------------------------------------------------------------------- /tests/10fuzzy/[result]/Jamroot: -------------------------------------------------------------------------------- 1 | X 2 | Y 3 | V 4 | Z -------------------------------------------------------------------------------- /tests/10fuzzyafter/10fuzzyafter.patch: -------------------------------------------------------------------------------- 1 | diff --git a/Jamroot b/Jamroot 2 | index a6981dd..0c08f09 100644 3 | --- a/Jamroot 4 | +++ b/Jamroot 5 | @@ -1,3 +1,4 @@ 6 | X 7 | Y 8 | +V 9 | W -------------------------------------------------------------------------------- /tests/10fuzzyafter/Jamroot: -------------------------------------------------------------------------------- 1 | X 2 | Y 3 | Z -------------------------------------------------------------------------------- /tests/10fuzzyafter/[result]/Jamroot: -------------------------------------------------------------------------------- 1 | X 2 | Y 3 | V 4 | Z -------------------------------------------------------------------------------- /tests/10fuzzybefore/10fuzzybefore.patch: -------------------------------------------------------------------------------- 1 | diff --git a/Jamroot b/Jamroot 2 | index a6981dd..0c08f09 100644 3 | --- a/Jamroot 4 | +++ b/Jamroot 5 | @@ -1,3 +1,4 @@ 6 | T 7 | YYYY 8 | +V 9 | Z -------------------------------------------------------------------------------- /tests/10fuzzybefore/Jamroot: -------------------------------------------------------------------------------- 1 | X 2 | Y 3 | Z -------------------------------------------------------------------------------- /tests/10fuzzybefore/[result]/Jamroot: -------------------------------------------------------------------------------- 1 | X 2 | Y 3 | V 4 | Z -------------------------------------------------------------------------------- /tests/11permission/11permission.patch: -------------------------------------------------------------------------------- 1 | --- some_file 2 | +++ some_file 3 | @@ -2,6 +2,7 @@ 4 | line 2 5 | line 3 6 | line 4 7 | +line 5 8 | line 6 9 | line 7 10 | line 8 11 | -------------------------------------------------------------------------------- /tests/11permission/[result]/some_file: -------------------------------------------------------------------------------- 1 | line 1 2 | line 2 3 | line 3 4 | line 4 5 | line 5 6 | line 6 7 | line 7 8 | line 8 9 | line 9 10 | line 10 11 | -------------------------------------------------------------------------------- /tests/11permission/some_file: -------------------------------------------------------------------------------- 1 | line 1 2 | line 2 3 | line 3 4 | line 4 5 | line 6 6 | line 7 7 | line 8 8 | line 9 9 | line 10 10 | -------------------------------------------------------------------------------- /tests/Descript.ion: -------------------------------------------------------------------------------- 1 | 01uni_multi.patch unified diff multiple files 2 | 02uni_newline.patch newline at the end of file 3 | 03trail_fname.patch trailing spaces in patch filenames 4 | 04can_patch.patch can be applied multiple times to the same file 5 | data various patch data samples 6 | 06nested.patch nested dirs 7 | 05hg_change.patch mercurial diff 8 | -------------------------------------------------------------------------------- /tests/data/autofix/absolute-path.diff: -------------------------------------------------------------------------------- 1 | Index: c:/winnt/tests/run_tests.py 2 | =================================================================== 3 | --- c:/winnt/tests/run_tests.py (revision 132) 4 | +++ c:/winnt/tests/run_tests.py (working copy) 5 | @@ -240,6 +240,12 @@ 6 | self.assertNotEqual(pto.parse(fp), True) 7 | fp.close() 8 | 9 | + def test_fail_absolute_path(self): 10 | + fp = open(join(tests_dir, "data/failing/absolute-path.diff")) 11 | + res = patch.PatchSet().parse(fp) 12 | + self.assertFalse(res) 13 | + fp.close() 14 | + 15 | class TestPatchApply(unittest.TestCase): 16 | def setUp(self): 17 | self.save_cwd = os.getcwdu() 18 | Index: c:/winnt/patch_ng.py 19 | =================================================================== 20 | --- c:/winnt/patch_ng.py (revision 132) 21 | +++ c:/winnt/patch_ng.py (working copy) 22 | @@ -22,7 +22,7 @@ 23 | from StringIO import StringIO 24 | import urllib2 25 | 26 | -from os.path import exists, isfile, abspath 27 | +from os.path import exists, isabs, isfile, abspath 28 | from os import unlink 29 | 30 | 31 | @@ -439,7 +439,21 @@ 32 | 33 | return (errors == 0) 34 | 35 | + def process_filenames(): 36 | + """ sanitize filenames 37 | + return True on success 38 | + """ 39 | + errors = 0 40 | 41 | + for i,p in enumerate(self.items): 42 | + # 43 | + 44 | + # absolute paths are not allowed 45 | + if isabs(p.source) or isabs(p.target): 46 | + warning("error: absolute paths are not allowed for patch no.%d" % i) 47 | + 48 | + return (errors == 0) 49 | + 50 | def apply(self): 51 | """ apply parsed patch 52 | return True on success 53 | -------------------------------------------------------------------------------- /tests/data/autofix/parent-path.diff: -------------------------------------------------------------------------------- 1 | Index: patch_ng.py 2 | =================================================================== 3 | --- ../patch_ng.py (revision 151) 4 | +++ ../patch_ng.py (working copy) 5 | @@ -13,7 +13,7 @@ 6 | """ 7 | 8 | __author__ = "techtonik.rainforce.org" 9 | -__version__ = "1.11.10-dev" 10 | +__version__ = "1.11.11-dev" 11 | 12 | import copy 13 | import logging 14 | -------------------------------------------------------------------------------- /tests/data/autofix/stripped-trailing-whitespace.diff: -------------------------------------------------------------------------------- 1 | Index: Python/peephole.c 2 | =================================================================== 3 | --- Python/peephole.c (revision 72970) 4 | +++ Python/peephole.c (working copy) 5 | @@ -180,11 +180,12 @@ 6 | } 7 | 8 | static int 9 | -fold_unaryops_on_constants(unsigned char *codestr, PyObject *consts) 10 | +fold_unaryops_on_constants(unsigned char *codestr, PyObject *consts, 11 | + PyObject *names) 12 | { 13 | PyObject *newconst=NULL, *v; 14 | Py_ssize_t len_consts; 15 | - int opcode; 16 | + int opcode, i = 1, j; 17 | 18 | /* Pre-conditions */ 19 | assert(PyList_CheckExact(consts)); 20 | @@ -205,6 +206,11 @@ 21 | case UNARY_INVERT: 22 | newconst = PyNumber_Invert(v); 23 | break; 24 | + case LOAD_ATTR: 25 | + i = 3; 26 | + newconst = PyObject_GetAttr(v, 27 | + PyTuple_GET_ITEM(names, GETARG(codestr, 3))); 28 | + break; 29 | default: 30 | /* Called with an unknown opcode */ 31 | PyErr_Format(PyExc_SystemError, 32 | @@ -226,9 +232,11 @@ 33 | Py_DECREF(newconst); 34 | 35 | /* Write NOP LOAD_CONST newconst */ 36 | - codestr[0] = NOP; 37 | - codestr[1] = LOAD_CONST; 38 | - SETARG(codestr, 1, len_consts); 39 | + for (j = 0; j < i; j++) { 40 | + codestr[j] = NOP; 41 | + } 42 | + codestr[i] = LOAD_CONST; 43 | + SETARG(codestr, i, len_consts); 44 | return 1; 45 | } 46 | 47 | @@ -484,10 +492,13 @@ 48 | case UNARY_NEGATIVE: 49 | case UNARY_CONVERT: 50 | case UNARY_INVERT: 51 | + case LOAD_ATTR: 52 | if (lastlc >= 1 && 53 | ISBASICBLOCK(blocks, i-3, 4) && 54 | - fold_unaryops_on_constants(&codestr[i-3], consts)) { 55 | - i -= 2; 56 | + fold_unaryops_on_constants(&codestr[i-3], consts, names)) { 57 | + if (opcode != LOAD_ATTR) { 58 | + i -= 2; 59 | + } 60 | assert(codestr[i] == LOAD_CONST); 61 | cumlc = 1; 62 | } 63 | -------------------------------------------------------------------------------- /tests/data/exotic/diff.py-python25.diff: -------------------------------------------------------------------------------- 1 | --- diff.py Sun Dec 27 16:08:28 2009 2 | +++ trunk/diff.py Sun Dec 27 15:46:58 2009 3 | @@ -7,7 +7,7 @@ 4 | 5 | """ 6 | 7 | -import sys, os, datetime, difflib, optparse 8 | +import sys, os, time, difflib, optparse 9 | 10 | def main(): 11 | 12 | @@ -29,8 +29,8 @@ 13 | n = options.lines 14 | fromfile, tofile = args 15 | 16 | - fromdate = datetime.datetime.fromtimestamp( os.stat(fromfile).st_mtime ).strftime(" ") 17 | - todate = datetime.datetime.fromtimestamp( os.stat(fromfile).st_mtime ).strftime(" ") 18 | + fromdate = time.ctime(os.stat(fromfile).st_mtime) 19 | + todate = time.ctime(os.stat(tofile).st_mtime) 20 | fromlines = open(fromfile, 'U').readlines() 21 | tolines = open(tofile, 'U').readlines() 22 | 23 | -------------------------------------------------------------------------------- /tests/data/exotic/dpatch.diff: -------------------------------------------------------------------------------- 1 | #! /bin/sh /usr/share/dpatch/dpatch-run 2 | ## 30_default_charset_utf8.dpatch by 3 | ## 4 | ## All lines beginning with `## DP:' are a description of the patch. 5 | ## DP: Use UTF-8 as default charset 6 | 7 | @DPATCH@ 8 | 9 | diff -uraN trac-0.11.5.orig/trac/mimeview/api.py trac-0.11.5/trac/mimeview/api.py 10 | --- trac-0.11.5.orig/trac/mimeview/api.py 2009-06-30 21:18:58.000000000 +0200 11 | +++ trac-0.11.5/trac/mimeview/api.py 2009-09-28 22:02:35.000000000 +0200 12 | @@ -579,7 +579,7 @@ 13 | annotators = ExtensionPoint(IHTMLPreviewAnnotator) 14 | converters = ExtensionPoint(IContentConverter) 15 | 16 | - default_charset = Option('trac', 'default_charset', 'iso-8859-15', 17 | + default_charset = Option('trac', 'default_charset', 'utf-8', 18 | """Charset to be used when in doubt.""") 19 | 20 | tab_width = IntOption('mimeviewer', 'tab_width', 8, 21 | -------------------------------------------------------------------------------- /tests/data/extended/git-added-new-empty-file.diff: -------------------------------------------------------------------------------- 1 | diff --git a/new-file.txt b/new-file.txt 2 | new file mode 100644 3 | index 0000000..e69de29 4 | -------------------------------------------------------------------------------- /tests/data/extended/svn-added-new-empty-file.diff: -------------------------------------------------------------------------------- 1 | Index: new-file.txt 2 | =================================================================== 3 | -------------------------------------------------------------------------------- /tests/data/failing/context-format.diff: -------------------------------------------------------------------------------- 1 | *** socketserver.py 2011-02-03 14:45:53.075396994 -0700 2 | --- socketserver-new.py 2011-02-03 14:57:53.185396998 -0700 3 | *************** 4 | *** 224,229 **** 5 | --- 224,232 ---- 6 | r, w, e = select.select([self], [], [], poll_interval) 7 | if self in r: 8 | self._handle_request_noblock() 9 | + 10 | + if getattr(self, "active_children", None) != None and len(self.active_children) > 0: 11 | + self.collect_children() 12 | finally: 13 | self.__shutdown_request = False 14 | self.__is_shut_down.set() 15 | *************** 16 | *** 521,527 **** 17 | 18 | def process_request(self, request, client_address): 19 | """Fork a new subprocess to process the request.""" 20 | - self.collect_children() 21 | pid = os.fork() 22 | if pid: 23 | # Parent process 24 | --- 524,529 ---- 25 | -------------------------------------------------------------------------------- /tests/data/failing/missing-hunk-line.diff: -------------------------------------------------------------------------------- 1 | Index: codereview/urls.py 2 | =================================================================== 3 | --- codereview/urls.py (revision 646) 4 | +++ codereview/urls.py (working copy) 5 | @@ -76,7 +76,8 @@ 6 | (r'^_ah/xmpp/message/chat/', 'incoming_chat'), 7 | (r'^_ah/mail/(.*)', 'incoming_mail'), 8 | (r'^xsrf_token$', 'xsrf_token'), 9 | - (r'^static/upload.py$', 'customized_upload_py'), 10 | + # patching upload.py on the fly 11 | + (r'^dynamic/upload.py$', 'customized_upload_py'), 12 | (r'^search$', 'search'), 13 | ) 14 | Index: templates/use_uploadpy.html 15 | =================================================================== 16 | --- templates/use_uploadpy.html (revision 646) 17 | +++ templates/use_uploadpy.html (working copy) 18 | @@ -2,7 +2,7 @@ 19 | 20 | {%block body%} 21 |

Tired of uploading files through the form?

22 | -

Download upload.py, a simple tool for 23 | +

Download upload.py, a simple tool for 24 | uploading diffs from a version control system to the codereview app.

25 | 26 |

Usage summary: 27 | y -------------------------------------------------------------------------------- /tests/data/failing/non-empty-patch-for-empty-file.diff: -------------------------------------------------------------------------------- 1 | Index: upload.py 2 | =================================================================== 3 | --- upload.py (revision 623) 4 | +++ upload.py (working copy) 5 | @@ -393,6 +393,9 @@ 6 | ## elif e.code >= 500 and e.code < 600: 7 | ## # Server Error - try again. 8 | ## continue 9 | + elif e.code == 404: 10 | + ErrorExit("Error: RPC call to %s failed with status 404\n" 11 | + "Check upload server is valid - %s" % (request_path, self.host)) 12 | else: 13 | raise 14 | finally: 15 | -------------------------------------------------------------------------------- /tests/data/failing/not-a-patch.log: -------------------------------------------------------------------------------- 1 | *get* '220 ftp.**********.com MadGoat FTP server V2.5-3 ready.\r\n' 2 | *resp* '220 ftp.**********.com MadGoat FTP server V2.5-3 ready.' 3 | *welcome* '220 ftp.**********.com MadGoat FTP server V2.5-3 ready.' 4 | *cmd* u'USER *******' 5 | *put* u'USER *******\r\n' 6 | *get* '331 Username "*******" Okay, need password.\r\n' 7 | *resp* '331 Username "*******" Okay, need password.' 8 | *cmd* u'PASS *******' 9 | *put* u'PASS *******\r\n' 10 | -------------------------------------------------------------------------------- /tests/data/failing/upload.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conan-io/python-patch-ng/885476a6e07048159654b8559cfbb2a6c7e28d96/tests/data/failing/upload.py -------------------------------------------------------------------------------- /tests/data/git-changed-2-files.diff: -------------------------------------------------------------------------------- 1 | diff --git a/jsonpickle/__init__.py b/jsonpickle/__init__.py 2 | index 22161dd..ea5ca6d 100644 3 | --- a/jsonpickle/__init__.py 4 | +++ b/jsonpickle/__init__.py 5 | @@ -87,7 +87,12 @@ class JSONPluginMgr(object): 6 | self._decoders = {} 7 | 8 | ## Options to pass to specific encoders 9 | - self._encoder_options = {} 10 | + json_opts = ((), {'sort_keys': True}) 11 | + self._encoder_options = { 12 | + 'json': json_opts, 13 | + 'simplejson': json_opts, 14 | + 'django.util.simplejson': json_opts, 15 | + } 16 | 17 | ## The exception class that is thrown when a decoding error occurs 18 | self._decoder_exceptions = {} 19 | diff --git a/tests/jsonpickle_test.py b/tests/jsonpickle_test.py 20 | index c61dec4..09ba339 100644 21 | --- a/tests/jsonpickle_test.py 22 | +++ b/tests/jsonpickle_test.py 23 | @@ -427,6 +427,15 @@ class PicklingTestCase(unittest.TestCase): 24 | inflated = self.unpickler.restore(flattened) 25 | self.assertEqual(obj, inflated) 26 | 27 | + def test_references(self): 28 | + obj_a = Thing('foo') 29 | + obj_b = Thing('bar') 30 | + coll = [obj_a, obj_b, obj_b] 31 | + flattened = self.pickler.flatten(coll) 32 | + inflated = self.unpickler.restore(flattened) 33 | + self.assertEqual(len(inflated), len(coll)) 34 | + for x in range(len(coll)): 35 | + self.assertEqual(repr(coll[x]), repr(inflated[x])) 36 | 37 | class JSONPickleTestCase(unittest.TestCase): 38 | def setUp(self): 39 | -------------------------------------------------------------------------------- /tests/data/git-changed-file.diff: -------------------------------------------------------------------------------- 1 | diff --git a/jsonpickle/__init__.py b/jsonpickle/__init__.py 2 | index 22161dd..ea5ca6d 100644 3 | --- a/jsonpickle/__init__.py 4 | +++ b/jsonpickle/__init__.py 5 | @@ -87,7 +87,12 @@ class JSONPluginMgr(object): 6 | self._decoders = {} 7 | 8 | ## Options to pass to specific encoders 9 | - self._encoder_options = {} 10 | + json_opts = ((), {'sort_keys': True}) 11 | + self._encoder_options = { 12 | + 'json': json_opts, 13 | + 'simplejson': json_opts, 14 | + 'django.util.simplejson': json_opts, 15 | + } 16 | 17 | ## The exception class that is thrown when a decoding error occurs 18 | self._decoder_exceptions = {} 19 | diff --git a/tests/jsonpickle_test.py b/tests/jsonpickle_test.py 20 | index c61dec4..09ba339 100644 21 | --- a/tests/jsonpickle_test.py 22 | +++ b/tests/jsonpickle_test.py 23 | @@ -427,6 +427,15 @@ class PicklingTestCase(unittest.TestCase): 24 | inflated = self.unpickler.restore(flattened) 25 | self.assertEqual(obj, inflated) 26 | 27 | + def test_references(self): 28 | + obj_a = Thing('foo') 29 | + obj_b = Thing('bar') 30 | + coll = [obj_a, obj_b, obj_b] 31 | + flattened = self.pickler.flatten(coll) 32 | + inflated = self.unpickler.restore(flattened) 33 | + self.assertEqual(len(inflated), len(coll)) 34 | + for x in range(len(coll)): 35 | + self.assertEqual(repr(coll[x]), repr(inflated[x])) 36 | 37 | class JSONPickleTestCase(unittest.TestCase): 38 | def setUp(self): 39 | -------------------------------------------------------------------------------- /tests/data/git-dash-in-filename.diff: -------------------------------------------------------------------------------- 1 | From fe0fecac607022e8a8017c5209b79e4fda342213 Mon Sep 17 00:00:00 2001 2 | From: "Dr. Matthias St. Pierre" 3 | Date: Thu, 10 Dec 2015 13:40:30 +0100 4 | Subject: [PATCH 2/2] modified hello-world.txt 5 | 6 | --- 7 | hello-world.txt | 4 +++- 8 | 1 file changed, 3 insertions(+), 1 deletion(-) 9 | 10 | diff --git a/hello-world.txt b/hello-world.txt 11 | index 3b18e51..dcc4fd9 100644 12 | --- a/hello-world.txt 13 | +++ b/hello-world.txt 14 | @@ -1 +1,3 @@ 15 | -hello world 16 | +hello world, 17 | +how are you? 18 | + 19 | -- 20 | 2.4.10 21 | 22 | -------------------------------------------------------------------------------- /tests/data/hg-added-file.diff: -------------------------------------------------------------------------------- 1 | diff -r f51cb906c4ad -r 0260994e7b44 tests/utils.py 2 | --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3 | +++ b/tests/utils.py Sat Aug 06 07:26:28 2011 +0200 4 | @@ -0,0 +1,55 @@ 5 | +# Copyright 2011 Google Inc. 6 | +# 7 | +# Licensed under the Apache License, Version 2.0 (the "License"); 8 | +# you may not use this file except in compliance with the License. 9 | +# You may obtain a copy of the License at 10 | +# 11 | +# http://www.apache.org/licenses/LICENSE-2.0 12 | +# 13 | +# Unless required by applicable law or agreed to in writing, software 14 | +# distributed under the License is distributed on an "AS IS" BASIS, 15 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | +# See the License for the specific language governing permissions and 17 | +# limitations under the License. 18 | + 19 | +"""Test utils.""" 20 | + 21 | +import os 22 | + 23 | +from google.appengine.ext import testbed 24 | + 25 | +from django.test import TestCase as _TestCase 26 | + 27 | + 28 | +class TestCase(_TestCase): 29 | + """Customized Django TestCase. 30 | + 31 | + This class disables the setup of Django features that are not 32 | + available on App Engine (e.g. fixture loading). And it initializes 33 | + the Testbad class provided by the App Engine SDK. 34 | + """ 35 | + 36 | + def _fixture_setup(self): # defined in django.test.TestCase 37 | + pass 38 | + 39 | + def _fixture_teardown(self): # defined in django.test.TestCase 40 | + pass 41 | + 42 | + def setUp(self): 43 | + super(TestCase, self).setUp() 44 | + self.testbed = testbed.Testbed() 45 | + self.testbed.activate() 46 | + self.testbed.init_datastore_v3_stub() 47 | + self.testbed.init_user_stub() 48 | + 49 | + def tearDown(self): 50 | + self.testbed.deactivate() 51 | + super(TestCase, self).tearDown() 52 | + 53 | + def login(self, email): 54 | + """Logs in a user identified by email.""" 55 | + os.environ['USER_EMAIL'] = email 56 | + 57 | + def logout(self): 58 | + """Logs the user out.""" 59 | + os.environ['USER_EMAIL'] = '' 60 | -------------------------------------------------------------------------------- /tests/data/hg-changed-2-files.diff: -------------------------------------------------------------------------------- 1 | diff -r b2d9961ff1f5 TODO 2 | --- a/TODO Sat Dec 26 16:36:37 2009 +0200 3 | +++ b/TODO Sun Dec 27 22:28:17 2009 +0200 4 | @@ -7,3 +7,7 @@ 5 | - remove files 6 | - svn diff 7 | - hg diff 8 | + 9 | +Source and target file conflicts: 10 | +- two same source files in the same patch 11 | +- one source and later one target file with the same name (that exists) 12 | diff -r b2d9961ff1f5 test commit/review system test 13 | --- a/test commit/review system test Sat Dec 26 16:36:37 2009 +0200 14 | +++ b/test commit/review system test Sun Dec 27 22:28:17 2009 +0200 15 | @@ -1,4 +1,4 @@ 16 | something to 17 | change in 18 | -this file 19 | -for review 20 | \ No newline at end of file 21 | +this file <-- this should be removed!!! ARRGH! BASTARD, HOW DARE YOU TO MESS WITH PROJECT HISTORY! 22 | +for review 23 | -------------------------------------------------------------------------------- /tests/data/hg-exported.diff: -------------------------------------------------------------------------------- 1 | # HG changeset patch 2 | # User ralf@brainbot.com 3 | # Date 1215097201 -7200 4 | # Branch xmlrpc_repr 5 | # Node ID 2f52c1a4e3ff26d4d7b391e7851792d4e47d8017 6 | # Parent 185779ba2591ba6249601209c8dd5750b6e14716 7 | implement _Method.__repr__/__str__ 8 | 9 | diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py 10 | --- a/Lib/test/test_xmlrpc.py 11 | +++ b/Lib/test/test_xmlrpc.py 12 | @@ -639,9 +639,31 @@ 13 | os.remove("xmldata.txt") 14 | os.remove(test_support.TESTFN) 15 | 16 | +class ReprTest(unittest.TestCase): 17 | + """ 18 | + calling repr/str on a _Method object should not consult the xmlrpc server 19 | + (http://bugs.python.org/issue1690840) 20 | + """ 21 | + def _make_method(self): 22 | + """return a _Method object, which when called raises a RuntimeError""" 23 | + def _raise_error(*args): 24 | + raise RuntimeError("called") 25 | + return xmlrpclib._Method(_raise_error, 'test') 26 | + 27 | + def test_method_repr(self): 28 | + m = self._make_method() 29 | + repr(m) 30 | + repr(xmlrpclib.ServerProxy('http://localhost:8000').doit) 31 | + 32 | + 33 | + def test_method_str(self): 34 | + m = self._make_method() 35 | + str(m) 36 | + str(xmlrpclib.ServerProxy('http://localhost:8000').doit) 37 | + 38 | def test_main(): 39 | xmlrpc_tests = [XMLRPCTestCase, HelperTestCase, DateTimeTestCase, 40 | - BinaryTestCase, FaultTestCase] 41 | + BinaryTestCase, FaultTestCase, ReprTest] 42 | 43 | # The test cases against a SimpleXMLRPCServer raise a socket error 44 | # 10035 (WSAEWOULDBLOCK) in the server thread handle_request call when 45 | diff --git a/Lib/xmlrpclib.py b/Lib/xmlrpclib.py 46 | --- a/Lib/xmlrpclib.py 47 | +++ b/Lib/xmlrpclib.py 48 | @@ -1191,7 +1191,10 @@ 49 | return _Method(self.__send, "%s.%s" % (self.__name, name)) 50 | def __call__(self, *args): 51 | return self.__send(self.__name, args) 52 | - 53 | + def __repr__(self): 54 | + return "<%s.%s %s %s>" % (self.__class__.__module__, self.__class__.__name__, self.__name, self.__send) 55 | + __str__ = __repr__ 56 | + 57 | ## 58 | # Standard transport class for XML-RPC over HTTP. 59 | #

60 | -------------------------------------------------------------------------------- /tests/data/svn-added-new-file-withcontent.diff: -------------------------------------------------------------------------------- 1 | Index: new-file.txt 2 | =================================================================== 3 | --- new-file.txt (revision 0) 4 | +++ new-file.txt (revision 0) 5 | @@ -0,0 +1 @@ 6 | +with content 7 | \ No newline at end of file 8 | -------------------------------------------------------------------------------- /tests/data/svn-changed-2-files.diff: -------------------------------------------------------------------------------- 1 | Index: trac/versioncontrol/svn_fs.py 2 | =================================================================== 3 | --- trac/versioncontrol/svn_fs.py (revision 8986) 4 | +++ trac/versioncontrol/svn_fs.py (working copy) 5 | @@ -289,7 +289,7 @@ 6 | repos = fs_repos 7 | else: 8 | repos = CachedRepository(self.env.get_db_cnx, fs_repos, None, 9 | - self.log) 10 | + self.log, self.env) 11 | repos.has_linear_changesets = True 12 | if authname: 13 | authz = SubversionAuthorizer(self.env, weakref.proxy(repos), 14 | Index: trac/versioncontrol/cache.py 15 | =================================================================== 16 | --- trac/versioncontrol/cache.py (revision 8986) 17 | +++ trac/versioncontrol/cache.py (working copy) 18 | @@ -18,7 +18,7 @@ 19 | import os 20 | import posixpath 21 | 22 | -from trac.core import TracError 23 | +from trac.core import * 24 | from trac.util.datefmt import utc, to_timestamp 25 | from trac.util.translation import _ 26 | from trac.versioncontrol import Changeset, Node, Repository, Authorizer, \ 27 | @@ -36,19 +36,42 @@ 28 | CACHE_METADATA_KEYS = (CACHE_REPOSITORY_DIR, CACHE_YOUNGEST_REV) 29 | 30 | 31 | +class ICacheChangesetListener(Interface): 32 | + """Cached changeset operations""" 33 | + 34 | + def edit_changeset(cset): 35 | + """Called when changeset is about to be cached. 36 | + Returns altered data to cache or None if unchanged. cset usually 37 | + contains cset.date, cset.author, cset.message and cset.rev 38 | + """ 39 | + 40 | +class CacheManager(Component): 41 | + """Provide interface to plug-in into cache operations""" 42 | + 43 | + observers = ExtensionPoint(ICacheChangesetListener) 44 | + 45 | + def check_changeset(self, cset): 46 | + for observer in self.observers: 47 | + res = observer.edit_changeset(cset) 48 | + if res != None: 49 | + cset = res 50 | + return cset 51 | + 52 | + 53 | class CachedRepository(Repository): 54 | 55 | has_linear_changesets = False 56 | 57 | scope = property(lambda self: self.repos.scope) 58 | 59 | - def __init__(self, getdb, repos, authz, log): 60 | + def __init__(self, getdb, repos, authz, log, env): 61 | Repository.__init__(self, repos.name, authz, log) 62 | if callable(getdb): 63 | self.getdb = getdb 64 | else: 65 | self.getdb = lambda: getdb 66 | self.repos = repos 67 | + self.cache_mgr = CacheManager(env) 68 | 69 | def close(self): 70 | self.repos.close() 71 | @@ -77,6 +100,7 @@ 72 | 73 | def sync_changeset(self, rev): 74 | cset = self.repos.get_changeset(rev) 75 | + cset = self.cache_mgr.check_changeset(cset) 76 | db = self.getdb() 77 | cursor = db.cursor() 78 | cursor.execute("UPDATE revision SET time=%s, author=%s, message=%s " 79 | @@ -182,6 +206,7 @@ 80 | self.log.info("Trying to sync revision [%s]" % 81 | next_youngest) 82 | cset = self.repos.get_changeset(next_youngest) 83 | + cset = self.cache_mgr.check_changeset(cset) 84 | try: 85 | cursor.execute("INSERT INTO revision " 86 | " (rev,time,author,message) " 87 | -------------------------------------------------------------------------------- /tests/data/svn-modified-empty-file.diff: -------------------------------------------------------------------------------- 1 | Index: upload.py 2 | =================================================================== 3 | --- upload.py (revision 120) 4 | +++ upload.py (working copy) 5 | @@ -0,0 +1 @@ 6 | +new info 7 | \ No newline at end of file 8 | -------------------------------------------------------------------------------- /tests/recoverage.bat: -------------------------------------------------------------------------------- 1 | cd .. 2 | python -m coverage run tests/run_tests.py 3 | python -m coverage html -d tests/coverage 4 | python -m coverage report -m 5 | -------------------------------------------------------------------------------- /tests/run_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | python-patch test suite 4 | 5 | There are two kind of tests: 6 | - file-based tests 7 | - directory-based tests 8 | - unit tests 9 | 10 | File-based test is patch file, initial file and resulting file 11 | for comparison. 12 | 13 | Directory-based test is a self-sufficient directory with: 14 | files to be patched, patch file itself and [result] dir. You can 15 | manually apply patch and compare outcome with [result] directory. 16 | This is what this test runner does. 17 | 18 | Unit tests test API and are all inside this runner. 19 | 20 | 21 | == Code Coverage == 22 | 23 | To refresh code coverage stats, get 'coverage' tool from 24 | http://pypi.python.org/pypi/coverage/ and run this file with: 25 | 26 | coverage run run_tests.py 27 | coverage html -d coverage 28 | 29 | On Windows it may be more convenient instead of `coverage` call 30 | `python -m coverage.__main__` 31 | """ 32 | from __future__ import print_function 33 | 34 | import os 35 | import sys 36 | import re 37 | import shutil 38 | import unittest 39 | import copy 40 | import stat 41 | from os import listdir, chmod 42 | from os.path import abspath, dirname, exists, join, isdir, isfile 43 | from tempfile import mkdtemp 44 | try: 45 | getcwdu = os.getcwdu 46 | except AttributeError: 47 | getcwdu = os.getcwd # python 3, where getcwd always returns a unicode object 48 | 49 | verbose = False 50 | if "-v" in sys.argv or "--verbose" in sys.argv: 51 | verbose = True 52 | 53 | 54 | # full path for directory with tests 55 | TESTS = dirname(abspath(__file__)) 56 | TESTDATA = join(TESTS, 'data') 57 | def testfile(name): 58 | return join(TESTDATA, name) 59 | 60 | 61 | # import patch_ng.py from parent directory 62 | save_path = sys.path 63 | sys.path.insert(0, dirname(TESTS)) 64 | import patch_ng 65 | sys.path = save_path 66 | 67 | 68 | # ---------------------------------------------------------------------------- 69 | class TestPatchFiles(unittest.TestCase): 70 | """ 71 | unittest hack - test* methods are generated by add_test_methods() function 72 | below dynamically using information about *.patch files from tests directory 73 | 74 | """ 75 | def _assert_files_equal(self, file1, file2): 76 | f1 = f2 = None 77 | try: 78 | f1 = open(file1, "rb") 79 | f2 = open(file2, "rb") 80 | for line in f1: 81 | self.assertEqual(line, f2.readline()) 82 | 83 | finally: 84 | if f2: 85 | f2.close() 86 | if f1: 87 | f1.close() 88 | 89 | def _assert_dirs_equal(self, dir1, dir2, ignore=[]): 90 | """ 91 | compare dir2 with reference dir1, ignoring entries 92 | from supplied list 93 | 94 | """ 95 | # recursive 96 | if type(ignore) == str: 97 | ignore = [ignore] 98 | e2list = [en for en in listdir(dir2) if en not in ignore] 99 | for e1 in listdir(dir1): 100 | if e1 in ignore: 101 | continue 102 | e1path = join(dir1, e1) 103 | e2path = join(dir2, e1) 104 | self.assertTrue(exists(e1path)) 105 | self.assertTrue(exists(e2path), "%s does not exist" % e2path) 106 | self.assertTrue(isdir(e1path) == isdir(e2path)) 107 | if not isdir(e1path): 108 | self._assert_files_equal(e1path, e2path) 109 | else: 110 | self._assert_dirs_equal(e1path, e2path, ignore=ignore) 111 | e2list.remove(e1) 112 | for e2 in e2list: 113 | self.fail("extra file or directory: %s" % e2) 114 | 115 | 116 | def _run_test(self, testname): 117 | """ 118 | boilerplate for running *.patch file tests 119 | """ 120 | 121 | # 1. create temp test directory 122 | # 2. copy files 123 | # 3. execute file-based patch 124 | # 4. compare results 125 | # 5. cleanup on success 126 | 127 | tmpdir = mkdtemp(prefix="%s."%testname) 128 | 129 | basepath = join(TESTS, testname) 130 | basetmp = join(tmpdir, testname) 131 | 132 | patch_file = basetmp + ".patch" 133 | 134 | file_based = isfile(basepath + ".from") 135 | from_tgt = basetmp + ".from" 136 | 137 | if file_based: 138 | shutil.copy(basepath + ".from", tmpdir) 139 | shutil.copy(basepath + ".patch", tmpdir) 140 | else: 141 | # directory-based 142 | for e in listdir(basepath): 143 | epath = join(basepath, e) 144 | if not isdir(epath): 145 | shutil.copy(epath, join(tmpdir, e)) 146 | else: 147 | shutil.copytree(epath, join(tmpdir, e)) 148 | 149 | 150 | # 3. 151 | # test utility as a whole 152 | patch_tool = join(dirname(TESTS), "patch_ng.py") 153 | save_cwd = getcwdu() 154 | os.chdir(tmpdir) 155 | extra = "-f" if "10fuzzy" in testname else "" 156 | if verbose: 157 | cmd = '%s %s %s "%s"' % (sys.executable, patch_tool, extra, patch_file) 158 | print("\n"+cmd) 159 | else: 160 | cmd = '%s %s -q %s "%s"' % (sys.executable, patch_tool, extra, patch_file) 161 | ret = os.system(cmd) 162 | assert ret == 0, "Error %d running test %s" % (ret, testname) 163 | os.chdir(save_cwd) 164 | 165 | 166 | # 4. 167 | # compare results 168 | if file_based: 169 | self._assert_files_equal(basepath + ".to", from_tgt) 170 | else: 171 | # recursive comparison 172 | self._assert_dirs_equal(join(basepath, "[result]"), 173 | tmpdir, 174 | ignore=["%s.patch" % testname, ".svn", ".gitkeep", "[result]"]) 175 | remove_tree_force(tmpdir) 176 | return 0 177 | 178 | 179 | def add_test_methods(cls): 180 | """ 181 | hack to generate test* methods in target class - one 182 | for each *.patch file in tests directory 183 | """ 184 | 185 | # list testcases - every test starts with number 186 | # and add them as test* methods 187 | testptn = re.compile(r"^(?P\d{2,}[^\.]+).*$") 188 | 189 | testset = [testptn.match(e).group('name') for e in listdir(TESTS) if testptn.match(e)] 190 | testset = sorted(set(testset)) 191 | 192 | for filename in testset: 193 | methname = 'test_' + filename 194 | def create_closure(): 195 | name = filename 196 | return lambda self: self._run_test(name) 197 | test = create_closure() 198 | setattr(cls, methname, test) 199 | if verbose: 200 | print("added test method %s to %s" % (methname, cls)) 201 | add_test_methods(TestPatchFiles) 202 | 203 | # ---------------------------------------------------------------------------- 204 | 205 | class TestCheckPatched(unittest.TestCase): 206 | def setUp(self): 207 | self.save_cwd = getcwdu() 208 | os.chdir(TESTS) 209 | 210 | def tearDown(self): 211 | os.chdir(self.save_cwd) 212 | 213 | def test_patched_multipatch(self): 214 | pto = patch_ng.fromfile("01uni_multi/01uni_multi.patch") 215 | os.chdir(join(TESTS, "01uni_multi", "[result]")) 216 | self.assertTrue(pto.can_patch(b"updatedlg.cpp")) 217 | 218 | def test_can_patch_single_source(self): 219 | pto2 = patch_ng.fromfile("02uni_newline.patch") 220 | self.assertTrue(pto2.can_patch(b"02uni_newline.from")) 221 | 222 | def test_can_patch_fails_on_target_file(self): 223 | pto3 = patch_ng.fromfile("03trail_fname.patch") 224 | self.assertEqual(None, pto3.can_patch(b"03trail_fname.to")) 225 | self.assertEqual(None, pto3.can_patch(b"not_in_source.also")) 226 | 227 | def test_multiline_false_on_other_file(self): 228 | pto = patch_ng.fromfile("01uni_multi/01uni_multi.patch") 229 | os.chdir(join(TESTS, "01uni_multi")) 230 | self.assertFalse(pto.can_patch(b"updatedlg.cpp")) 231 | 232 | def test_single_false_on_other_file(self): 233 | pto3 = patch_ng.fromfile("03trail_fname.patch") 234 | self.assertFalse(pto3.can_patch("03trail_fname.from")) 235 | 236 | def test_can_patch_checks_source_filename_even_if_target_can_be_patched(self): 237 | pto2 = patch_ng.fromfile("04can_patch.patch") 238 | self.assertFalse(pto2.can_patch("04can_patch_ng.to")) 239 | 240 | # ---------------------------------------------------------------------------- 241 | 242 | class TestPatchParse(unittest.TestCase): 243 | def test_fromstring(self): 244 | try: 245 | f = open(join(TESTS, "01uni_multi/01uni_multi.patch"), "rb") 246 | readstr = f.read() 247 | finally: 248 | f.close() 249 | pst = patch_ng.fromstring(readstr) 250 | self.assertEqual(len(pst), 5) 251 | 252 | def test_fromfile(self): 253 | pst = patch_ng.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) 254 | self.assertNotEqual(pst, False) 255 | self.assertEqual(len(pst), 5) 256 | ps2 = patch_ng.fromfile(testfile("failing/not-a-patch.log")) 257 | self.assertFalse(ps2) 258 | 259 | def test_no_header_for_plain_diff_with_single_file(self): 260 | pto = patch_ng.fromfile(join(TESTS, "03trail_fname.patch")) 261 | self.assertEqual(pto.items[0].header, []) 262 | 263 | def test_header_for_second_file_in_svn_diff(self): 264 | pto = patch_ng.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) 265 | self.assertEqual(pto.items[1].header[0], b'Index: updatedlg.h\r\n') 266 | self.assertTrue(pto.items[1].header[1].startswith(b'=====')) 267 | 268 | def test_hunk_desc(self): 269 | pto = patch_ng.fromfile(testfile('git-changed-file.diff')) 270 | self.assertEqual(pto.items[0].hunks[0].desc, b'class JSONPluginMgr(object):') 271 | 272 | def test_autofixed_absolute_path(self): 273 | pto = patch_ng.fromfile(join(TESTS, "data/autofix/absolute-path.diff")) 274 | self.assertEqual(pto.errors, 0) 275 | self.assertEqual(pto.warnings, 9) 276 | self.assertEqual(pto.items[0].source, b"winnt/tests/run_tests.py") 277 | 278 | def test_autofixed_parent_path(self): 279 | # [ ] exception vs return codes for error recovery 280 | # [x] separate return code when patch lib compensated the error 281 | # (implemented as warning count) 282 | pto = patch_ng.fromfile(join(TESTS, "data/autofix/parent-path.diff")) 283 | self.assertEqual(pto.errors, 0) 284 | self.assertEqual(pto.warnings, 4) 285 | self.assertEqual(pto.items[0].source, b"patch_ng.py") 286 | 287 | def test_autofixed_stripped_trailing_whitespace(self): 288 | pto = patch_ng.fromfile(join(TESTS, "data/autofix/stripped-trailing-whitespace.diff")) 289 | self.assertEqual(pto.errors, 0) 290 | self.assertEqual(pto.warnings, 4) 291 | 292 | def test_fail_missing_hunk_line(self): 293 | fp = open(join(TESTS, "data/failing/missing-hunk-line.diff"), 'rb') 294 | pto = patch_ng.PatchSet() 295 | self.assertNotEqual(pto.parse(fp), True) 296 | fp.close() 297 | 298 | def test_fail_context_format(self): 299 | fp = open(join(TESTS, "data/failing/context-format.diff"), 'rb') 300 | res = patch_ng.PatchSet().parse(fp) 301 | self.assertFalse(res) 302 | fp.close() 303 | 304 | def test_fail_not_a_patch(self): 305 | fp = open(join(TESTS, "data/failing/not-a-patch.log"), 'rb') 306 | res = patch_ng.PatchSet().parse(fp) 307 | self.assertFalse(res) 308 | fp.close() 309 | 310 | def test_diffstat(self): 311 | output = """\ 312 | updatedlg.cpp | 20 ++++++++++++++++++-- 313 | updatedlg.h | 1 + 314 | manifest.xml | 15 ++++++++------- 315 | conf.cpp | 23 +++++++++++++++++------ 316 | conf.h | 7 ++++--- 317 | 5 files changed, 48 insertions(+), 18 deletions(-), +1203 bytes""" 318 | pto = patch_ng.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) 319 | self.assertEqual(pto.diffstat(), output, "Output doesn't match") 320 | 321 | 322 | class TestPatchSetDetection(unittest.TestCase): 323 | def test_svn_detected(self): 324 | pto = patch_ng.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) 325 | self.assertEqual(pto.type, patch_ng.SVN) 326 | 327 | # generate tests methods for TestPatchSetDetection - one for each patch file 328 | def generate_detection_test(filename, patchtype): 329 | # saving variable in local scope to prevent test() 330 | # from fetching it from global 331 | patchtype = difftype 332 | def test(self): 333 | pto = patch_ng.fromfile(join(TESTDATA, filename)) 334 | self.assertEqual(pto.type, patchtype) 335 | return test 336 | 337 | for filename in os.listdir(TESTDATA): 338 | if isdir(join(TESTDATA, filename)): 339 | continue 340 | 341 | difftype = patch_ng.PLAIN 342 | if filename.startswith('git-'): 343 | difftype = patch_ng.GIT 344 | if filename.startswith('hg-'): 345 | difftype = patch_ng.HG 346 | if filename.startswith('svn-'): 347 | difftype = patch_ng.SVN 348 | 349 | name = 'test_'+filename 350 | test = generate_detection_test(filename, difftype) 351 | setattr(TestPatchSetDetection, name, test) 352 | if verbose: 353 | print("added test method %s to %s" % (name, 'TestPatchSetDetection')) 354 | 355 | 356 | class TestPatchApply(unittest.TestCase): 357 | def setUp(self): 358 | self.save_cwd = getcwdu() 359 | self.tmpdir = mkdtemp(prefix=self.__class__.__name__) 360 | os.chdir(self.tmpdir) 361 | 362 | def tearDown(self): 363 | os.chdir(self.save_cwd) 364 | remove_tree_force(self.tmpdir) 365 | 366 | def tmpcopy(self, filenames): 367 | """copy file(s) from test_dir to self.tmpdir""" 368 | for f in filenames: 369 | shutil.copy(join(TESTS, f), self.tmpdir) 370 | 371 | def test_apply_returns_false_on_failure(self): 372 | self.tmpcopy(['data/failing/non-empty-patch-for-empty-file.diff', 373 | 'data/failing/upload.py']) 374 | pto = patch_ng.fromfile('non-empty-patch-for-empty-file.diff') 375 | self.assertFalse(pto.apply()) 376 | 377 | def test_apply_returns_true_on_success(self): 378 | self.tmpcopy(['03trail_fname.patch', 379 | '03trail_fname.from']) 380 | pto = patch_ng.fromfile('03trail_fname.patch') 381 | self.assertTrue(pto.apply()) 382 | 383 | def test_revert(self): 384 | def get_file_content(filename): 385 | with open(filename, 'rb') as f: 386 | return f.read() 387 | 388 | self.tmpcopy(['03trail_fname.patch', 389 | '03trail_fname.from']) 390 | pto = patch_ng.fromfile('03trail_fname.patch') 391 | self.assertTrue(pto.apply()) 392 | self.assertNotEqual(get_file_content(self.tmpdir + '/03trail_fname.from'), 393 | get_file_content(TESTS + '/03trail_fname.from')) 394 | self.assertTrue(pto.revert()) 395 | self.assertEqual(get_file_content(self.tmpdir + '/03trail_fname.from'), 396 | get_file_content(TESTS + '/03trail_fname.from')) 397 | 398 | def test_apply_root(self): 399 | treeroot = join(self.tmpdir, 'rootparent') 400 | shutil.copytree(join(TESTS, '06nested'), treeroot) 401 | pto = patch_ng.fromfile(join(TESTS, '06nested/06nested.patch')) 402 | self.assertTrue(pto.apply(root=treeroot)) 403 | 404 | def test_apply_strip(self): 405 | treeroot = join(self.tmpdir, 'rootparent') 406 | shutil.copytree(join(TESTS, '06nested'), treeroot) 407 | pto = patch_ng.fromfile(join(TESTS, '06nested/06nested.patch')) 408 | for p in pto: 409 | p.source = b'nasty/prefix/' + p.source 410 | p.target = b'nasty/prefix/' + p.target 411 | self.assertTrue(pto.apply(strip=2, root=treeroot)) 412 | 413 | def test_create_file(self): 414 | treeroot = join(self.tmpdir, 'rootparent') 415 | os.makedirs(treeroot) 416 | pto = patch_ng.fromfile(join(TESTS, '08create/08create.patch')) 417 | pto.apply(strip=0, root=treeroot) 418 | self.assertTrue(os.path.exists(os.path.join(treeroot, 'created'))) 419 | 420 | def test_delete_file(self): 421 | treeroot = join(self.tmpdir, 'rootparent') 422 | shutil.copytree(join(TESTS, '09delete'), treeroot) 423 | pto = patch_ng.fromfile(join(TESTS, '09delete/09delete.patch')) 424 | pto.apply(strip=0, root=treeroot) 425 | self.assertFalse(os.path.exists(os.path.join(treeroot, 'deleted'))) 426 | 427 | def test_fuzzy_patch_both(self): 428 | treeroot = join(self.tmpdir, 'rootparent') 429 | shutil.copytree(join(TESTS, '10fuzzy'), treeroot) 430 | pto = patch_ng.fromfile(join(TESTS, '10fuzzy/10fuzzy.patch')) 431 | self.assertTrue(pto.apply(root=treeroot, fuzz=True)) 432 | self.assertFalse(pto.apply(root=treeroot, fuzz=False)) 433 | 434 | def test_fuzzy_patch_before(self): 435 | treeroot = join(self.tmpdir, 'rootparent') 436 | shutil.copytree(join(TESTS, '10fuzzybefore'), treeroot) 437 | pto = patch_ng.fromfile(join(TESTS, '10fuzzybefore/10fuzzybefore.patch')) 438 | self.assertTrue(pto.apply(root=treeroot, fuzz=True)) 439 | self.assertFalse(pto.apply(root=treeroot, fuzz=False)) 440 | 441 | def test_fuzzy_patch_after(self): 442 | treeroot = join(self.tmpdir, 'rootparent') 443 | shutil.copytree(join(TESTS, '10fuzzyafter'), treeroot) 444 | pto = patch_ng.fromfile(join(TESTS, '10fuzzyafter/10fuzzyafter.patch')) 445 | self.assertTrue(pto.apply(root=treeroot, fuzz=True)) 446 | self.assertFalse(pto.apply(root=treeroot, fuzz=False)) 447 | 448 | def test_unlink_backup_windows(self): 449 | """ Apply patch to a read-only file and don't change its filemode 450 | """ 451 | treeroot = join(self.tmpdir, 'rootparent') 452 | shutil.copytree(join(TESTS, '11permission'), treeroot) 453 | pto = patch_ng.fromfile(join(TESTS, '11permission', '11permission.patch')) 454 | some_file = join(treeroot, 'some_file') 455 | chmod(some_file, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) 456 | self.assertTrue(pto.apply(root=treeroot)) 457 | self.assertTrue(os.stat(some_file).st_mode, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) 458 | 459 | 460 | class TestHelpers(unittest.TestCase): 461 | # unittest setting 462 | longMessage = True 463 | 464 | absolute = [b'/', b'c:\\', b'c:/', b'\\', b'/path', b'c:\\path'] 465 | relative = [b'path', b'path:\\', b'path:/', b'path\\', b'path/', b'path\\path'] 466 | 467 | def test_xisabs(self): 468 | for path in self.absolute: 469 | self.assertTrue(patch_ng.xisabs(path), 'Target path: ' + repr(path)) 470 | for path in self.relative: 471 | self.assertFalse(patch_ng.xisabs(path), 'Target path: ' + repr(path)) 472 | 473 | def test_xnormpath(self): 474 | path = b"../something/..\\..\\file.to.patch" 475 | self.assertEqual(patch_ng.xnormpath(path), b'../../file.to.patch') 476 | 477 | def test_xstrip(self): 478 | for path in self.absolute[:4]: 479 | self.assertEqual(patch_ng.xstrip(path), b'') 480 | for path in self.absolute[4:6]: 481 | self.assertEqual(patch_ng.xstrip(path), b'path') 482 | # test relative paths are not affected 483 | for path in self.relative: 484 | self.assertEqual(patch_ng.xstrip(path), path) 485 | 486 | def test_pathstrip(self): 487 | self.assertEqual(patch_ng.pathstrip(b'path/to/test/name.diff', 2), b'test/name.diff') 488 | self.assertEqual(patch_ng.pathstrip(b'path/name.diff', 1), b'name.diff') 489 | self.assertEqual(patch_ng.pathstrip(b'path/name.diff', 0), b'path/name.diff') 490 | 491 | def remove_tree_force(folder): 492 | for root, _, files in os.walk(folder): 493 | for it in files: 494 | chmod(os.path.join(root, it), stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) 495 | shutil.rmtree(folder, ignore_errors=True) 496 | 497 | # ---------------------------------------------------------------------------- 498 | 499 | if __name__ == '__main__': 500 | unittest.main() 501 | --------------------------------------------------------------------------------