├── .github ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── publish-release.yml │ └── py-lint.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── MANIFEST.in ├── README.md ├── appveyor.yml ├── codecov.yml ├── coverage.sh ├── docs ├── LICENSE └── README.rst ├── lib ├── __init__.py └── ufodiff │ ├── __init__.py │ ├── app.py │ ├── settings.py │ ├── subcommands │ ├── __init__.py │ ├── delta.py │ └── diff.py │ └── utilities │ ├── __init__.py │ └── ufo.py ├── release.sh ├── requirements.txt ├── setup.cfg ├── setup.py ├── test_runner.sh ├── tests ├── __init__.py ├── test_delta.py ├── test_diff.py ├── test_main.py ├── test_ufo.py ├── test_utilities.py └── testfiles │ ├── depth2 │ ├── depth3 │ │ ├── depth4 │ │ │ └── testdepth4.txt │ │ └── testdepth3.txt │ └── testdepth2.txt │ └── testfile.txt └── tox.ini /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [master] 9 | schedule: 10 | - cron: '0 0 * * 1' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | # Override automatic language detection by changing the below list 21 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 22 | language: ['python'] 23 | # Learn more... 24 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v2 29 | with: 30 | # We must fetch at least the immediate parents so that if this is 31 | # a pull request then we can checkout the head. 32 | fetch-depth: 2 33 | 34 | # If this run was triggered by a pull request event, then checkout 35 | # the head of the pull request instead of the merge commit. 36 | - run: git checkout HEAD^2 37 | if: ${{ github.event_name == 'pull_request' }} 38 | 39 | # Initializes the CodeQL tools for scanning. 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@v1 42 | with: 43 | languages: ${{ matrix.language }} 44 | # If you wish to specify custom queries, you can do so here or in a config file. 45 | # By default, queries listed here will override any specified in a config file. 46 | # Prefix the list here with "+" to use these queries and those in the config file. 47 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 48 | 49 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 50 | # If this step fails, then you should remove it and run the build manually (see below) 51 | - name: Autobuild 52 | uses: github/codeql-action/autobuild@v1 53 | 54 | # ℹ️ Command-line programs to run using the OS shell. 55 | # 📚 https://git.io/JvXDl 56 | 57 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 58 | # and modify them (or add more) to build your code if your project 59 | # uses a compiled language 60 | 61 | #- run: | 62 | # make bootstrap 63 | # make release 64 | 65 | - name: Perform CodeQL Analysis 66 | uses: github/codeql-action/analyze@v1 67 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | # Sequence of patterns matched against refs/tags 4 | tags: 5 | - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | name: Create and Publish Release 8 | 9 | jobs: 10 | build: 11 | name: Create and Publish Release 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: [3.8] 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install --upgrade setuptools wheel twine 26 | - name: Create GitHub release 27 | id: create_release 28 | uses: actions/create-release@v1 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | with: 32 | tag_name: ${{ github.ref }} 33 | release_name: ${{ github.ref }} 34 | body: | 35 | Please see the root of the repository for the CHANGELOG.md 36 | draft: false 37 | prerelease: false 38 | - name: Build and publish to PyPI 39 | env: 40 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 41 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 42 | run: | 43 | python setup.py sdist bdist_wheel 44 | twine upload dist/* 45 | -------------------------------------------------------------------------------- /.github/workflows/py-lint.yml: -------------------------------------------------------------------------------- 1 | name: Python Lints 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest] 11 | python-version: [3.8] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - name: Install Python testing dependencies 20 | run: pip install --upgrade flake8 21 | - name: flake8 Lint 22 | uses: py-actions/flake8@v1 23 | with: 24 | max-line-length: "90" 25 | path: "lib/ufodiff" 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | .hypothesis/ 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | #Ipython Notebook 61 | .ipynb_checkpoints 62 | 63 | # PyCharm files 64 | .idea/ 65 | 66 | # Project files 67 | tests/runner.py 68 | tests/profiler.py 69 | 70 | # Test directories 71 | .pytest_cache 72 | 73 | .venv 74 | 75 | # VSCode config 76 | .vscode -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.org 2 | 3 | sudo: false 4 | language: python 5 | matrix: 6 | include: 7 | - python: 3.6 8 | env: TOX_ENV=py36 9 | - python: 3.7 10 | env: TOX_ENV=py37 11 | - python: 3.8 12 | env: TOX_ENV=py38 13 | - os: osx 14 | language: generic 15 | osx_image: xcode11 # Python 3.7.4 running on macOS 10.14.4 16 | install: pip3 install --upgrade tox pytest 17 | env: TOX_ENV=py37 18 | script: tox -e $TOX_ENV 19 | - python: 3.7 20 | dist: xenial 21 | env: TOX_ENV=coverage 22 | install: 23 | - pip install --upgrade coverage pytest mock 24 | - pip install . 25 | script: 26 | - coverage run -m pytest 27 | - coverage report -m 28 | after_success: 29 | - bash <(curl -s https://codecov.io/bash) 30 | 31 | script: tox -e $TOX_ENV 32 | 33 | install: 34 | - pip install --upgrade tox pytest 35 | 36 | notifications: 37 | email: false 38 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### v1.0.3 4 | 5 | - add CodeQL static code analysis 6 | - update gitpython dependency to v3.1.10 7 | 8 | ### v1.0.2 9 | 10 | - fix: deprecated string literal escapes refactored to raw strings. ufo.py module (deprecated as of Py3.6) 11 | - reformat import statements based with default isort formatting 12 | - refactor all Python source to achieve line lengths < 90 13 | - add flake8 lint testing 14 | - update gitpython dependency to v3.1.8 15 | 16 | ### v1.0.1 17 | 18 | - update gitpython dependency to v3.1.5 - decreases package size by removing tests from distribution 19 | - update gitdb dependency to v4.0.5 20 | - update smmap dependency to v3.0.4 21 | - add dependabot configuration file 22 | 23 | ### v1.0.0 24 | 25 | - v1 release 26 | - eliminate Py2.7 support 27 | - remove unnecessary import statement in `subcommands.diff` module 28 | - black source formatting 29 | - update Python dependencies 30 | - broaden pinned depdendency definitions in requirements.txt file 31 | - use requirements.txt dependency versions in CI testing 32 | - add macOS CI testing 33 | 34 | ### v0.5.4 35 | 36 | - updated gitpython dependency to version 2.1.11 37 | 38 | ### v0.5.3 39 | 40 | - updated gitpython dependency to version 2.1.10 41 | - added license to Python wheel distributions 42 | - removed Travis CI testing, added Semaphore CI testing 43 | 44 | ### v0.5.2 45 | 46 | - eliminated support for Python v2.6 47 | - updated gitpython dependency to version 2.1.9 48 | 49 | ### v0.5.1 50 | 51 | - updated gitpython dependency to version 2.1.7 52 | 53 | ### v0.5.0 54 | 55 | - added support for full UFO v3 spec to all commands 56 | - fixed bug in the branch diff reports, now comparison branch relative to current branch (comparison..current) as indicated in the documentation 57 | - updated/cleaned diff report strings 58 | 59 | ### v0.4.1 60 | 61 | - fix for Markdown text error (branch list item formatting) 62 | 63 | ### v0.4.0 64 | 65 | - added support for branch vs. branch analysis to the `delta`, `deltajson`, and `deltamd` commands 66 | - updated `delta`, `deltajson`, and `deltamd` argument validations 67 | - bugfix for the git diff performed with the `diff` and `diffnc` commands (`branch...branch` to `branch..branch`) 68 | 69 | ### v0.3.0 70 | 71 | - `diff` subcommand added to support colored text diffs between UFO spec source files 72 | - `diffnc` subcommand added to support uncolored text diffs between UFO spec source files 73 | - fixed argument description errors in the in-app `--help` documentation 74 | 75 | ### v0.2.4 76 | 77 | - bug fix for commit SHA1 digest parsing on Windows 78 | - refactored DeltaFilePathDict class 79 | 80 | ### v0.2.3 81 | 82 | - bug fix for Python 3 string comparison issue in ufodiff.utilities.ufo module 83 | 84 | ### v0.2.2 85 | 86 | - PyPI documentation update 87 | 88 | ### v0.2.1 89 | 90 | - added `deltamd` subcommand to support output of Markdown formatted delta file reports 91 | - added short SHA1 digests for the commit history under analysis 92 | - Commit SHA1 digests added to `delta` subcommand as header of standard output string 93 | - Commit SHA1 digests added to `deltajson` subcommand JSON string with key `commits` 94 | 95 | ### v0.2.0 96 | 97 | - initial release with support for the following subcommands: 98 | - `ufodiff delta all` 99 | - `ufodiff deltajson all` 100 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include docs/LICENSE 2 | include docs/README.rst 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![PyPI](https://img.shields.io/pypi/v/ufodiff?color=blueviolet&label=PyPI&logo=python&logoColor=white)](https://pypi.org/project/ufodiff) 4 | [![Build Status](https://travis-ci.com/source-foundry/ufodiff.svg?branch=master)](https://travis-ci.com/source-foundry/ufodiff) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/o2vdn1uf7uxau3o7/branch/master?svg=true)](https://ci.appveyor.com/project/chrissimpkins/ufodiff/branch/master) 6 | ![Python Lints](https://github.com/source-foundry/ufodiff/workflows/Python%20Lints/badge.svg) 7 | [![codecov](https://codecov.io/gh/source-foundry/ufodiff/branch/master/graph/badge.svg)](https://codecov.io/gh/source-foundry/ufodiff) 8 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/23e4187ff4474576b7e3334075180202)](https://app.codacy.com/app/SourceFoundry/ufodiff) 9 | 10 | ufodiff is a command line UFO source file diff tool for collaborative typeface development projects. 11 | 12 | It examines git repositories for changes to files that are part of the UFO source spec only (i.e. all file changes in the repository external to the UFO source code are not considered by the tool). It supports reporting of UFO source file additions, deletions, and modifications as well as colored and uncolored text diffs for UFO source files that were modified between branches or across one or more commits. 13 | 14 | UFO versions 2 and 3 are fully supported in the current release. 15 | 16 | 17 | 18 | ## Contents 19 | 20 | - [Contents](#contents) 21 | - [Install](#install) 22 | - [Quickstart Examples](#quickstart-examples) 23 | - [Plain Text](#plain-text) 24 | - [Markdown](#markdown) 25 | - [JSON](#json) 26 | - [Colored text diff](#colored-text-diff) 27 | - [_For terminals with ANSI color code support_](#for-terminals-with-ansi-color-code-support) 28 | - [Uncolored text diff](#uncolored-text-diff) 29 | - [Usage](#usage) 30 | - [View Results in Terminal](#view-results-in-terminal) 31 | - [Piping Data to Other Applications](#piping-data-to-other-applications) 32 | - [File Writes](#file-writes) 33 | - [ufodiff Subcommands](#ufodiff-subcommands) 34 | - [Subcommand List](#subcommand-list) 35 | - [Issues](#issues) 36 | - [License](#license) 37 | 38 | ## Install 39 | 40 | Installation with `pip` is recommended: 41 | 42 | `$ pip install ufodiff` 43 | 44 | Upgrade a previous installation with: 45 | 46 | `$ pip install --upgrade ufodiff` 47 | 48 | ## Quickstart Examples 49 | 50 |

List UFO source file additions, deletions, and modifications

51 | 52 | #### Plain Text 53 | 54 | For the last two commits: 55 | 56 | ``` 57 | $ ufodiff delta all commits:2 58 | ``` 59 | 60 | For the last five commits: 61 | 62 | ``` 63 | $ ufodiff delta all commits:5 64 | ``` 65 | 66 | For the last five commits, only from the Test-Regular.ufo source directory (note: not a filepath to source, only include UFO directory name): 67 | 68 | ``` 69 | $ ufodiff delta all commits:5 Test-Regular.ufo 70 | ``` 71 | 72 | Between the current branch and the `development` branch 73 | 74 | ``` 75 | # ufodiff delta all branch:development 76 | ``` 77 | 78 | #### Markdown 79 | 80 | For the last two commits in current branch: 81 | 82 | ``` 83 | $ ufodiff deltamd all commits:2 84 | ``` 85 | 86 | For the last five commits in current branch: 87 | 88 | ``` 89 | $ ufodiff deltamd all commits:5 90 | ``` 91 | 92 | For the last five commits in current branch, only from the Test-Regular.ufo source directory (note: not a filepath to source, only include UFO directory name): 93 | 94 | ``` 95 | $ ufodiff deltamd all commits:5 Test-Regular.ufo 96 | ``` 97 | 98 | Between the current branch and the `development` branch 99 | 100 | ``` 101 | # ufodiff deltamd all branch:development 102 | ``` 103 | 104 | #### JSON 105 | 106 | For the last two commits in current branch: 107 | 108 | ``` 109 | $ ufodiff deltajson all commits:2 110 | ``` 111 | 112 | For the last five commits in current branch: 113 | 114 | ``` 115 | $ ufodiff deltajson all commits:5 116 | ``` 117 | 118 | For the last five commits in current branch, only from the Test-Regular.ufo source directory (note: not a filepath to source, only include UFO directory name): 119 | 120 | ``` 121 | $ ufodiff deltajson all commits:5 Test-Regular.ufo 122 | ``` 123 | 124 | Between the current branch and the `development` branch 125 | 126 | ``` 127 | # ufodiff deltajson all branch:development 128 | ``` 129 | 130 |

Text diff UFO source files

131 | 132 | #### Colored text diff 133 | 134 | ##### _For terminals with ANSI color code support_ 135 | 136 | All modified UFO files, last two commits: 137 | 138 | ``` 139 | $ ufodiff diff commits:2 140 | ``` 141 | 142 | All modified UFO files, last five commits: 143 | 144 | ``` 145 | $ ufodiff diff commits:5 146 | ``` 147 | 148 | All modified files, current branch vs. development branch: 149 | 150 | ``` 151 | $ ufodiff diff branch:development 152 | ``` 153 | 154 | #### Uncolored text diff 155 | 156 | All modified UFO files, last two commits: 157 | 158 | ``` 159 | $ ufodiff diffnc commits:2 160 | ``` 161 | 162 | All modified UFO files, last five commits: 163 | 164 | ``` 165 | $ ufodiff diffnc commits:5 166 | ``` 167 | 168 | All modified files, current branch vs. development branch: 169 | 170 | ``` 171 | $ ufodiff diffnc branch:development 172 | ``` 173 | 174 | ## Usage 175 | 176 | `ufodiff` is a command line executable. Application features are accessed via subcommands to the `ufodiff` executable. 177 | 178 | Execute `ufodiff` inside the git repository where you develop your typeface. It will recursively test for the repository root from up to 4 levels of repository directory depth. If you are receiving exceptions due to inability to instantiate your git repository object, try bumping your working directory 179 | up a few levels closer to the root. There are otherwise no restrictions to where `ufodiff` is executed inside the repository. The source directory does not need to be the current working directory. 180 | 181 | #### View Results in Terminal 182 | 183 | By default, data are displayed in your terminal. Use one of the following approaches to either pipe data to another application or write data to a file. 184 | 185 | #### Piping Data to Other Applications 186 | 187 | On Unix/Linux/OS X platforms, use the `|` idiom to pipe the standard output stream from `ufodiff` to another application for further processing like this: 188 | 189 | ``` 190 | $ ufodiff deltajson all commits:3 | anotherapp --dosomething-with-json 191 | ``` 192 | 193 | #### File Writes 194 | 195 | On Unix/Linux/OS X platforms, use the `>` idiom to write the data in the standard output stream to a filepath like this: 196 | 197 | ``` 198 | $ ufodiff delta all commits:1 > myfont_delta.txt 199 | ``` 200 | 201 | ### ufodiff Subcommands 202 | 203 | #### Subcommand List 204 | 205 | - [delta](#delta) 206 | - [deltajson](#deltajson) 207 | - [deltamd](#deltamd) 208 | - [diff](#diff) 209 | - [diffnc](#diffnc) 210 | 211 | The commit history for all commands is compared with the `HEAD~N` git idiom. The branch comparisons across all commands are performed with the `test_branch..current_branch` git idiom. 212 | 213 |

delta

214 | 215 | `ufo delta` generates file modification, addition, and deletion reports over a user specified number of commits or across git branches. The data are streamed in plain text format through standard output with indicators for the type of file change. 216 | 217 | The file change indicators include: 218 | 219 | - **[A]** file added 220 | - **[D]** file deleted 221 | - **[M]** file modified 222 | 223 | For Markdown formatted data, see the `deltamd` command. For JSON formatted data, see the `deltajson` subcommand. 224 | 225 | The syntax is: 226 | 227 | ``` 228 | ufodiff delta [all] [commits:[N] | branch:[name]] 229 | ``` 230 | 231 | where `N` is an integer value that represents the number of commits in the git commit history to examine and `name` is the name of an existing git branch in the repository. These are mutually exclusive arguments. 232 | 233 | _Examples_: 234 | 235 | ``` 236 | $ ufodiff delta all commits:3 237 | $ ufodiff delta all commits:5 238 | $ ufodiff delta all commits:3 Test-Regular.ufo 239 | $ ufodiff delta all branch:development 240 | $ ufodiff delta all branch:development Test-Regular.ufo 241 | ``` 242 | 243 | Increase or decrease the integer value after the `commits:` argument to change the depth of the git commit history that you want to examine. Include an existing git branch name following the `branch:` argument to perform a branch vs. branch comparison. 244 | 245 | Add one or more optional UFO source base directory names (e.g. Font-Regular.ufo) as last positional arguments in your command to filter the delta analysis by individual source directories. 246 | 247 |

deltajson

248 | 249 | `ufo deltajson` generates file modification, addition, and deletion reports over a user specified number of commits or across git branches. The data are streamed in JSON format through standard output. 250 | 251 | For plain text formatted data, see the `delta` subcommand. For Markdown formatted data, see the `deltamd` command. 252 | 253 | The syntax is: 254 | 255 | ``` 256 | ufodiff deltajson [all] [commits:[N] | branch:[name]] 257 | ``` 258 | 259 | where `N` is an integer value that represents the number of commits in the git commit history to examine and `name` is the name of an existing git branch in the repository. These are mutually exclusive arguments. 260 | 261 | _Examples_: 262 | 263 | ``` 264 | $ ufodiff deltajson all commits:3 265 | $ ufodiff deltajson all commits:5 266 | $ ufodiff deltajson all commits:3 Test-Regular.ufo 267 | $ ufodiff deltajson all branch:development 268 | $ ufodiff deltajson all branch:development Test-Regular.ufo 269 | ``` 270 | 271 | JSON data for commit history analyses are formatted as: 272 | 273 | ```json 274 | { 275 | "commits": ["25087a1ab", "27fdb2e48", "6edab459e"], 276 | "added": ["filepath 1", "filepath 2", "filepath 3"], 277 | "deleted": ["filepath 1", "filepath 2", "filepath 3"], 278 | "modified": ["filepath 1", "filepath 2", "filepath 3"] 279 | } 280 | ``` 281 | 282 | JSON data for branch vs. branch analyses are formatted as: 283 | 284 | ```json 285 | { 286 | "branches": ["branch 1", "branch 2"], 287 | "added": ["filepath 1", "filepath 2", "filepath 3"], 288 | "deleted": ["filepath 1", "filepath 2", "filepath 3"], 289 | "modified": ["filepath 1", "filepath 2", "filepath 3"] 290 | } 291 | ``` 292 | 293 | Increase or decrease the integer value after the `commits:` argument to change the depth of the git commit history that you want to examine. Include an existing git branch name following the `branch:` argument to perform a branch vs. branch comparison. 294 | 295 | Add one or more optional UFO source base directory name (e.g. Font-Regular.ufo) as last positional arguments in your command to filter the delta analysis by individual source directories. 296 | 297 |

deltamd

298 | 299 | `ufodiff deltamd` generates file modification, addition, and deletion reports over a user specified number of commits or across git branches. The data are streamed in Github flavored Markdown format through standard output. 300 | 301 | For plain text formatted data, see the `delta` command. For JSON formatted data, see the `deltajson` command. 302 | 303 | The syntax is: 304 | 305 | ``` 306 | ufodiff deltamd [all] [commits:[N] | branch:[name]] 307 | ``` 308 | 309 | where `N` is an integer value that represents the number of commits in the git commit history to examine and `name` is an existing git branch name for a branch vs. branch comparison. 310 | 311 | _Examples_: 312 | 313 | ``` 314 | $ ufodiff deltamd all commits:3 315 | $ ufodiff deltamd all commits:5 316 | $ ufodiff deltamd all commits:3 Test-Regular.ufo 317 | ``` 318 | 319 | Increase or decrease the integer value after the `commits:` argument to change the depth of the git commit history that you want to examine. Include an existing git branch name following the `branch:` argument to perform a branch vs. branch comparison. 320 | 321 | Add one or more optional UFO source base directory name (e.g. Font-Regular.ufo) as last positional arguments in your command to filter the delta analysis by individual source directories. 322 | 323 |

diff

324 | 325 | `ufodiff diff` provides colored text diffs for all UFO files that were modified across one or more commits in the working branch, or between the HEAD of the working branch and any other branch in the repository. 326 | 327 | For uncolored diffs, see the `diffnc` command. 328 | 329 | The command syntax is: 330 | 331 | ``` 332 | ufodiff diff [commits:[N] | branch:[name]] 333 | ``` 334 | 335 | where `N` is an integer value that represents the number of commits in the git commit history to examine and `name` is the name of an existing git branch in the repository. These are mutually exclusive arguments. 336 | 337 | _Examples_: 338 | 339 | ``` 340 | $ ufodiff diff commits:2 341 | $ ufodiff diff branch:master 342 | ``` 343 | 344 |

diffnc

345 | 346 | `ufodiff diffnc` provides uncolored text diffs for all UFO files that were modified across one or more commits in the working branch, or between the HEAD of the working branch and any other branch in the repository. 347 | 348 | For colored diffs intended for use in terminals that support ANSI color codes, see the `diff` command. 349 | 350 | The command syntax is: 351 | 352 | ``` 353 | ufodiff diffnc [commits:[N] | branch:[name]] 354 | ``` 355 | 356 | where `N` is an integer value that represents the number of commits in the git commit history to examine and `name` is the name of an existing git branch in the repository. These are mutually exclusive arguments. 357 | 358 | _Examples_: 359 | 360 | ``` 361 | $ ufodiff diffnc commits:2 362 | $ ufodiff diffnc branch:master 363 | ``` 364 | 365 | ## Issues 366 | 367 | Please submit bug reports and feature requests as an [issue report](https://github.com/source-foundry/ufodiff/issues/new) on our Github repository. 368 | 369 | ## License 370 | 371 | [MIT License](https://github.com/source-foundry/ufodiff/blob/master/docs/LICENSE) 372 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - JOB: "3.8 32-bit" 4 | PYTHON_HOME: "C:\\Python38" 5 | TOX_PY: py38 6 | 7 | - JOB: "3.6 64-bit" 8 | PYTHON_HOME: "C:\\Python36-x64" 9 | TOX_PY: py36 10 | 11 | - JOB: "3.7 64-bit" 12 | PYTHON_HOME: "C:\\Python37-x64" 13 | TOX_PY: py37 14 | 15 | - JOB: "3.8 64-bit" 16 | PYTHON_HOME: "C:\\Python38-x64" 17 | TOX_PY: py38 18 | 19 | install: 20 | # Prepend Python to the PATH of this build 21 | - "SET PATH=%PYTHON_HOME%;%PYTHON_HOME%\\Scripts;%PATH%" 22 | 23 | # check that we have the expected version and architecture for Python 24 | - "python --version" 25 | - 'python -c "import struct; print(struct.calcsize(''P'') * 8)"' 26 | 27 | # upgrade pip and setuptools to avoid out-of-date warnings 28 | - "python -m pip install --disable-pip-version-check --user --upgrade pip setuptools virtualenv" 29 | 30 | # install the dependencies to run the tests 31 | - "python -m pip install tox" 32 | 33 | build: false 34 | 35 | test_script: 36 | - "tox -e %TOX_PY%" 37 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | max_report_age: off -------------------------------------------------------------------------------- /coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | coverage run --source ufodiff -m py.test 4 | coverage report -m 5 | coverage html 6 | 7 | #coverage xml 8 | #codecov --token=$CODECOV_UFODIFF 9 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Christopher Simpkins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /docs/README.rst: -------------------------------------------------------------------------------- 1 | ufodiff 2 | ======= 3 | 4 | ufodiff is a command line UFO source file modification and diff tool for collaborative typeface design/development projects. 5 | 6 | It examines git repositories for changes to files that are part of the UFO source spec only (i.e. all file changes in the repository external to the UFO source code are not considered by the tool). 7 | 8 | Documentation, source repository, and issue reporting available on `Github `_ . -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/source-foundry/ufodiff/548a1c427322a05ba9b592d62f6f10eb565029d9/lib/__init__.py -------------------------------------------------------------------------------- /lib/ufodiff/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | -------------------------------------------------------------------------------- /lib/ufodiff/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ==================================================== 5 | # Copyright 2018 Christopher Simpkins 6 | # MIT License 7 | # ==================================================== 8 | 9 | """ 10 | The app.py module defines a main() function that includes the logic for the 11 | `ufodiff` command line executable. 12 | 13 | This command line executable provides file modification (add, delete, modified) 14 | and text diff reports 15 | for files and directory structure that are part of the Unified Font Object (UFO) 16 | specification. These source files are used in typeface development. 17 | """ 18 | 19 | import os 20 | import sys 21 | 22 | from commandlines import Command 23 | from standardstreams import stderr, stdout 24 | 25 | from ufodiff import ( 26 | settings, 27 | ) # defines application version, help string, version string, usage string 28 | from ufodiff.subcommands.delta import Delta 29 | from ufodiff.subcommands.diff import Diff 30 | from ufodiff.utilities import dir_exists 31 | 32 | 33 | def main(): 34 | """Defines the logic for the `ufodiff` command line executable""" 35 | c = Command() 36 | 37 | if c.does_not_validate_missing_args(): 38 | stderr( 39 | "[ufodiff] ERROR: Please include the appropriate arguments with your command." 40 | ) 41 | sys.exit(1) 42 | 43 | if c.is_help_request(): 44 | stdout(settings.HELP) 45 | sys.exit(0) 46 | elif c.is_version_request(): 47 | stdout(settings.VERSION) 48 | sys.exit(0) 49 | elif c.is_usage_request(): 50 | stdout(settings.USAGE) 51 | sys.exit(0) 52 | 53 | # DELTA + DELTAJSON + DELTAMD sub-commands 54 | if c.subcmd in {"delta", "deltajson", "deltamd"}: 55 | # argument validation 56 | validate_delta_commands_args(c) 57 | # create list for UFO filtered analyses as requested by user 58 | ufo_directory_list = [] 59 | for arg in c.argv: 60 | if arg.endswith(".ufo"): 61 | ufo_directory_list.append(arg) 62 | 63 | # flags for type of test 64 | is_branch_test = False 65 | is_commits_test = False 66 | 67 | if c.arg2.startswith("commits:"): 68 | is_commits_test = True 69 | commits_list = c.arg2.split(":") 70 | commit_number = commits_list[1] 71 | elif c.arg2.startswith("branch:"): 72 | is_branch_test = True 73 | branch_list = c.arg2.split(":") 74 | branch_name = branch_list[1] 75 | # else: 76 | # pass # TODO: add direct git idiom call support 77 | 78 | # recursive search for the root of the git repository x 3 levels 79 | # if not found in working directory 80 | verified_gitroot_path = get_git_root_path() 81 | 82 | # perform the delta analysis on the repository, different object 83 | # for commits vs branch tests 84 | if is_commits_test is True: 85 | delta = Delta( 86 | verified_gitroot_path, 87 | ufo_directory_list, 88 | is_commit_test=True, 89 | commit_number=commit_number, 90 | ) 91 | elif is_branch_test is True: 92 | delta = Delta( 93 | verified_gitroot_path, 94 | ufo_directory_list, 95 | is_branch_test=True, 96 | compare_branch_name=branch_name, 97 | ) 98 | 99 | if c.arg1 == "all": 100 | if c.subcmd == "delta": 101 | sys.stdout.write(delta.get_stdout_string(write_format="text")) 102 | elif c.subcmd == "deltajson": 103 | sys.stdout.write(delta.get_stdout_string(write_format="json")) 104 | elif c.subcmd == "deltamd": 105 | sys.stdout.write(delta.get_stdout_string(write_format="markdown")) 106 | # TODO: implement glyph only command handling with 'ufo delta glyph' 107 | # elif c.arg1 == "glyph": 108 | # pass 109 | # TODO: implement nonglyph only command handling with 'ufo delta nonglyph' 110 | # elif c.arg1 == "nonglyph": 111 | # pass 112 | # DIFF SUBCOMMAND 113 | elif c.subcmd == "diff": 114 | # argument validation 115 | validate_diff_commands_args(c) 116 | # execute the command 117 | try: 118 | verified_gitroot_path = get_git_root_path() 119 | diff = Diff(verified_gitroot_path, color_diff=True) 120 | for diff_string in diff.get_diff_string_generator(c.arg1): 121 | stdout(diff_string) 122 | except Exception as e: 123 | stderr( 124 | "[ufodiff] ERROR: Unable to excecute your request. Error returned as: " 125 | + os.linesep 126 | + str(e) 127 | ) 128 | sys.exit(1) 129 | # DIFFNC SUBCOMMAND 130 | elif c.subcmd == "diffnc": 131 | # argument validations 132 | validate_diff_commands_args(c) 133 | # execute the command 134 | try: 135 | verified_gitroot_path = get_git_root_path() 136 | diff = Diff(verified_gitroot_path, color_diff=False) 137 | for diff_string in diff.get_diff_string_generator(c.arg1): 138 | stdout(diff_string) 139 | except Exception as e: 140 | stderr( 141 | "[ufodiff] ERROR: Unable to excecute your request. Error returned as: " 142 | + os.linesep 143 | + str(e) 144 | ) 145 | sys.exit(1) 146 | # # DIFF-FILE SUBCOMMAND 147 | # user specified file/directory filters on the diff performed 148 | # elif c.subcmd == "diff-filter": 149 | # pass 150 | # # DIFF-FILENC SUBCOMMAND 151 | # elif c.subcmd == "diff-filternc": 152 | # pass 153 | sys.exit(0) 154 | 155 | 156 | # Command Line Utility Functions 157 | 158 | 159 | def validate_delta_commands_args(command_obj): 160 | """ 161 | Validates arguments for any delta/deltajson/deltamd command requested 162 | by user. It provides user error messages and raises SystemExit for 163 | erroneous command entry at the command line. 164 | 165 | :param command_obj: a commandlines library Command object 166 | :return: no return object, SystemExit raised for all errors detected 167 | """ 168 | # used in command line argument validations 169 | acceptable_deltacommands = ["all"] 170 | # Command line argument validations 171 | if command_obj.argc < 3: # expected argument number 172 | stderr("[ufodiff] ERROR: Missing arguments.") 173 | sys.exit(1) 174 | if ( 175 | command_obj.arg1 not in acceptable_deltacommands 176 | ): # acceptable sub command to delta 177 | stderr( 178 | "[ufodiff] ERROR: 'ufodiff " 179 | + command_obj.arg0 180 | + " " 181 | + command_obj.arg1 182 | + "' is not a valid request" 183 | ) 184 | stderr("Acceptable arguments to " + command_obj.arg0 + " include:") 185 | for acceptable_deltacommand in acceptable_deltacommands: 186 | stderr(" " + acceptable_deltacommand) 187 | sys.exit(1) 188 | if command_obj.arg2.startswith("commits:"): 189 | commits_list = command_obj.arg2.split(":") 190 | if ( 191 | len(commits_list) == 2 and commits_list[1] == "" 192 | ): # does not include a value following the colon 193 | stderr( 194 | "[ufodiff] ERROR: Please include an integer value following " 195 | "the 'commits:' argument" 196 | ) 197 | sys.exit(1) 198 | else: 199 | commits_number = commits_list[1] 200 | validate_commit_number(commits_number) 201 | elif command_obj.arg2.startswith("branch:"): 202 | branch_list = command_obj.arg2.split(":") 203 | if len(branch_list) == 2 and branch_list[1] == "": 204 | stderr( 205 | "[ufodiff] ERROR: Please include the name of an existing git " 206 | "branch following the 'branch:' argument" 207 | ) 208 | sys.exit(1) 209 | else: 210 | stderr( 211 | "[ufodiff] ERROR: Please include either the 'commits:' or " 212 | "'branch:' argument in the command" 213 | ) 214 | sys.exit(1) 215 | 216 | 217 | def validate_diff_commands_args(command_obj): 218 | """ 219 | Validates arguments for any diff/diffnc command requested by user. 220 | It provides user error messages and raises SystemExit for erroneous 221 | command entry at the command line. 222 | 223 | :param command_obj: a commandlines library Command object 224 | :return: no return object, SystemExit raised for all errors detected 225 | """ 226 | # argument validations 227 | if command_obj.argc < 2: 228 | stderr("[ufodiff] ERROR: Missing arguments.") 229 | sys.exit(1) 230 | if command_obj.arg1.startswith("commits:"): 231 | if len(command_obj.arg1) < 9: 232 | stderr( 233 | "[ufodiff] ERROR: Please include an integer after the " 234 | "colon in the 'commits:[number]' argument" 235 | ) 236 | sys.exit(1) 237 | else: 238 | commits_list = command_obj.arg1.split(":") 239 | commits_number = commits_list[1] 240 | # validate the number of commits as an integer value, exit 241 | # with error message if not an integer 242 | validate_commit_number(commits_number) 243 | if command_obj.arg1.startswith("branch:"): 244 | if len(command_obj.arg1) < 8: 245 | stderr( 246 | "[ufodiff] ERROR: Please include the name of a git branch " 247 | "following the colon in the 'branch:[name]' argument" 248 | ) 249 | sys.exit(1) 250 | 251 | 252 | def validate_commit_number(commits_number): 253 | """ 254 | Validates commit number entered by user following `commits:` argument 255 | as valid integer and within appropriate range 256 | 257 | :param commits_number: string that was entered by user following `commits:` argument 258 | :return: no return object, raises SystemExit for all errors detected 259 | """ 260 | if ( 261 | not commits_number.isdigit() 262 | ): # validate that user entered number of commits for diff is an integer 263 | stderr( 264 | "[ufodiff] ERROR: The value following the colon in the 'commits:[number]' " 265 | "argument is not a valid integer value" 266 | ) 267 | sys.exit(1) 268 | elif ( 269 | int(commits_number) == 0 or int(commits_number) < 0 270 | ): # validate that the number of commits is > 0 271 | stderr( 272 | "[ufodiff] ERROR: Please define a value over zero for the commit history" 273 | ) 274 | sys.exit(1) 275 | 276 | 277 | def get_git_root_path(): 278 | """ 279 | Recursively searches for git root path over 4 directory levels above working directory 280 | :return: validated git root path as string OR raises SystemExit if not found 281 | """ 282 | try: 283 | # begin by defining current working directory as root of git repository 284 | unverified_gitroot_path = os.path.abspath(".") 285 | 286 | # check to see if this assumption is correct 287 | if dir_exists(os.path.join(unverified_gitroot_path, ".git")): 288 | verified_gitroot_path = os.path.join(unverified_gitroot_path, ".git") 289 | # if not, recursive search up to three directories above for the git repo root 290 | else: 291 | one_level_up = os.path.abspath( 292 | os.path.join(unverified_gitroot_path, os.pardir) 293 | ) 294 | two_levels_up = os.path.dirname(one_level_up) 295 | three_levels_up = os.path.dirname(two_levels_up) 296 | 297 | one_level_up_path = os.path.join(one_level_up, ".git") 298 | two_levels_up_path = os.path.join(two_levels_up, ".git") 299 | three_levels_up_path = os.path.join(three_levels_up, ".git") 300 | 301 | if dir_exists(one_level_up_path): # check one directory level up 302 | verified_gitroot_path = os.path.dirname(one_level_up_path) 303 | elif dir_exists(two_levels_up_path): # check two directory levels up 304 | verified_gitroot_path = os.path.dirname(two_levels_up_path) 305 | elif dir_exists(three_levels_up_path): # check three directory levels up 306 | verified_gitroot_path = os.path.dirname(three_levels_up_path) 307 | else: 308 | stderr( 309 | "[ufodiff] ERROR: Unable to identify the root of your git " 310 | "repository. Please try again from the root of your repository" 311 | ) 312 | sys.exit(1) 313 | except Exception as e: 314 | stderr( 315 | "[ufodiff] ERROR: Unable to identify the root of your git repository. " 316 | "Please try again from the root of your repository. " + str(e) 317 | ) 318 | sys.exit(1) 319 | 320 | return verified_gitroot_path 321 | -------------------------------------------------------------------------------- /lib/ufodiff/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ==================================================== 5 | # Copyright 2018 Christopher Simpkins 6 | # MIT License 7 | # ==================================================== 8 | 9 | # ------------------------------------------------------------------------------ 10 | # Library Name 11 | # ------------------------------------------------------------------------------ 12 | lib_name = "ufodiff" 13 | 14 | # ------------------------------------------------------------------------------ 15 | # Version Number 16 | # ------------------------------------------------------------------------------ 17 | major_version = "1" 18 | minor_version = "0" 19 | patch_version = "4" 20 | 21 | # ------------------------------------------------------------------------------ 22 | # Help String 23 | # ------------------------------------------------------------------------------ 24 | 25 | HELP = """==================================================== 26 | ufodiff 27 | Copyright 2018 Christopher Simpkins 28 | MIT License 29 | Source: https://github.com/source-foundry/ufodiff 30 | ==================================================== 31 | 32 | ufodiff is a UFO source file text diff and file modification reporting tool for \ 33 | collaborative typeface development. 34 | 35 | Subcommands: 36 | 37 | - delta --- UFO source file add/del/mod report as plain text 38 | - all 39 | - deltajson --- UFO source file add/del/mod report as JSON 40 | - all 41 | - deltamd --- UFO source file add/del/mod report as Markdown 42 | - all 43 | - diff --- colored text diff of UFO spec files (only) 44 | - diffnc --- uncolored text diff of UFO spec files (only) 45 | 46 | Syntax: 47 | ufodiff delta all [commits:[N] | branch:[name]] 48 | ufodiff deltajson all [commits:[N] | branch:[name]] 49 | ufodiff deltamd all [commits:[N] | branch:[name]] 50 | ufodiff diff [commits:[N] | branch:[name]] 51 | ufodiff diffnc [commits:[N] | branch:[name]] 52 | 53 | Increase or decrease integer value after the `commits:` argument to analyze across \ 54 | that number of commits in the commit history. 55 | 56 | Include an existing git branch for comparison with your current branch after the \ 57 | `branch:` argument. 58 | """ 59 | 60 | # ------------------------------------------------------------------------------ 61 | # Usage String 62 | # ------------------------------------------------------------------------------ 63 | 64 | USAGE = "ufodiff [subcommand] [subcommand arguments]" 65 | 66 | # ------------------------------------------------------------------------------ 67 | # Version String 68 | # ------------------------------------------------------------------------------ 69 | 70 | VERSION = "ufodiff v" + major_version + "." + minor_version + "." + patch_version 71 | -------------------------------------------------------------------------------- /lib/ufodiff/subcommands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/source-foundry/ufodiff/548a1c427322a05ba9b592d62f6f10eb565029d9/lib/ufodiff/subcommands/__init__.py -------------------------------------------------------------------------------- /lib/ufodiff/subcommands/delta.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ==================================================== 5 | # Copyright 2018 Christopher Simpkins 6 | # MIT License 7 | # ==================================================== 8 | 9 | import json 10 | import os 11 | 12 | from git import Repo 13 | 14 | from ufodiff.settings import major_version, minor_version, patch_version 15 | from ufodiff.utilities.ufo import Ufo 16 | 17 | 18 | class Delta(object): 19 | """ 20 | Delta class stores delta subcommand data and provides methods to support creation of 21 | delta / deltajson / deltamd application subcommand reports through UFO file spec 22 | validation and filtering of user requested *.ufo source directory filters 23 | 24 | Uses DeltaFilepathStringDict object (this module) to (1) filter data by optional user 25 | specified *.ufo source filter; (2) maintain final data for reports in the 26 | DeltaFilepathStringDict.delta_dict Python dictionary property 27 | 28 | :param gitrepo_path: (string) absolute file path to the root level of the git 29 | repository for analysis (automatically detected in ufodiff.app.py) 30 | :param ufo_directory_list: (list) list of one or more UFO directories for filter 31 | of results (user specified on CL) 32 | :param is_commit_test: (boolean) flag for request as test of commit history in 33 | the git repository 34 | :param commit_number: (string) the number of commits in git commit history for 35 | analysis as string (user specified) 36 | :param is_branch_test: (boolean) flag for request as test of branch vs. branch 37 | in git repository 38 | :param compare_branch_name: (string) the branch name requested by user for test 39 | vs. current branch (user specified) 40 | """ 41 | 42 | def __init__( 43 | self, 44 | gitrepo_path, 45 | ufo_directory_list, 46 | is_commit_test=False, 47 | commit_number="0", 48 | is_branch_test=False, 49 | compare_branch_name=None, 50 | ): 51 | # path to root of git repository 52 | self.gitrepo_path = gitrepo_path 53 | # used to filter results by user defined UFO source directory 54 | self.ufo_directory_list = ufo_directory_list 55 | # commit test flag 56 | self.is_commit_test = is_commit_test 57 | self.commit_number = ( 58 | commit_number # user defined number of commits in git history to compare 59 | ) 60 | # branch test flag 61 | self.is_branch_test = is_branch_test 62 | self.compare_branch_name = ( 63 | compare_branch_name # comparison branch requested by user (if branch test) 64 | ) 65 | # defined in _define_and_validate_ufo_diff_lists if branch test 66 | self.current_branch_name = "" 67 | # Ufo class used for UFO source validations 68 | self.ufo = Ufo() 69 | # GitPython object (instantiated in class method) 70 | self.git = None 71 | # stores delta file strings in .delta_dict attribute 72 | self.delta_fp_string_dict = DeltaFilepathStringDict(ufo_directory_list) 73 | 74 | # used to create dictionaries of report data in class methods 75 | self.commit_sha1_list = [] 76 | 77 | # file string lists 78 | # used to create dictionaries of report data in class methods 79 | self.added_ufo_file_list = [] 80 | # used to create dictionaries of report data in class methods 81 | self.modified_ufo_file_list = [] 82 | # used to create dictionaries of report data in class methods 83 | self.deleted_ufo_file_list = [] 84 | 85 | # filters files for UFO spec and includes only diff files that are part of spec 86 | # defines many of the class properties on object instantiation 87 | self._define_and_validate_ufo_diff_lists() 88 | 89 | # PRIVATE METHODS 90 | def _define_and_validate_ufo_diff_lists(self): 91 | """ 92 | Defines Delta class properties on instantiation of the object in app.py module 93 | :return: no return object 94 | """ 95 | # instantiate git Repo object 96 | repo = Repo(self.gitrepo_path) 97 | # define class attribute git object 98 | self.git = repo.git 99 | 100 | if self.is_commit_test is True: 101 | commit_number_string = "HEAD~" + self.commit_number # with HEAD~N syntax 102 | added_file_string = self.git.diff( 103 | "--name-only", "--diff-filter=A", commit_number_string 104 | ) 105 | added_filepath_list = added_file_string.split("\n") 106 | deleted_file_string = self.git.diff( 107 | "--name-only", "--diff-filter=D", commit_number_string 108 | ) 109 | deleted_filepath_list = deleted_file_string.split("\n") 110 | modified_file_string = self.git.diff( 111 | "--name-only", "--diff-filter=M", commit_number_string 112 | ) 113 | modified_filepath_list = modified_file_string.split("\n") 114 | elif self.is_branch_test is True: 115 | self.current_branch_name = self.git.rev_parse(["--abbrev-ref", "HEAD"]) 116 | branch_comparison_string = ( 117 | self.compare_branch_name + ".." + self.current_branch_name 118 | ) 119 | added_file_string = self.git.diff( 120 | ["--name-only", "--diff-filter=A", branch_comparison_string] 121 | ) 122 | added_filepath_list = added_file_string.split("\n") 123 | deleted_file_string = self.git.diff( 124 | ["--name-only", "--diff-filter=D", branch_comparison_string] 125 | ) 126 | deleted_filepath_list = deleted_file_string.split("\n") 127 | modified_file_string = self.git.diff( 128 | ["--name-only", "--diff-filter=M", branch_comparison_string] 129 | ) 130 | modified_filepath_list = modified_file_string.split("\n") 131 | 132 | # load class attribute lists with the filepaths that are validated to be UFO 133 | # in the following method 134 | self._validate_ufo_and_load_dict_from_filepath_strings( 135 | added_filepath_list, deleted_filepath_list, modified_filepath_list 136 | ) 137 | 138 | def _add_commit_sha1_to_lists(self): 139 | """ 140 | Adds commit SHA1 short codes for commits requested by user to the 141 | Delta.commit_sha1_list property as a Python list. 142 | 143 | :return: no return object 144 | """ 145 | sha1_num_commits = "-" + self.commit_number 146 | sha1_args = [sha1_num_commits, "--pretty=%h"] 147 | # git log -[N] --pretty=%h ===> newline delimited list of SHA1 x N commit 148 | sha1_string = self.git.log(sha1_args) 149 | # do not modify to os.linesep, Win fails tests with this change 150 | self.commit_sha1_list = sha1_string.split("\n") 151 | 152 | def _validate_ufo_and_load_dict_from_filepath_strings( 153 | self, added_filepath_list, deleted_filepath_list, modified_filepath_list 154 | ): 155 | """ 156 | Validates filepaths for detected added, modified, and deleted files against 157 | UFO specification and includes valid files in Delta object property lists. 158 | These lists are used to generate the DeltaFilepathStringDict delta_dict Python 159 | dictionary that maintains these data in key:value format for report generation. 160 | 161 | UFO validation of files occurs in this class method. 162 | 163 | :param added_filepath_list: (list) files that were added to repository 164 | :param deleted_filepath_list: (list) files that were deleted from repository 165 | :param modified_filepath_list: (list) files that were modified in repository 166 | :return: no return object 167 | """ 168 | # test for valid UFO files and add the filepath string to the appropriate class 169 | # instance attribute 170 | # load added files list 171 | if len(added_filepath_list) > 0: 172 | for added_file in added_filepath_list: 173 | if self.ufo.validate_file(added_file) is True: 174 | self.added_ufo_file_list.append(added_file) 175 | 176 | # load deleted files list 177 | if len(deleted_filepath_list) > 0: 178 | for deleted_file in deleted_filepath_list: 179 | if self.ufo.validate_file(deleted_file) is True: 180 | self.deleted_ufo_file_list.append(deleted_file) 181 | 182 | # load modified files list 183 | if len(modified_filepath_list) > 0: 184 | for modified_file in modified_filepath_list: 185 | if self.ufo.validate_file(modified_file) is True: 186 | self.modified_ufo_file_list.append(modified_file) 187 | 188 | # define the key:value structure of the dictionary attribute on the 189 | # DeltaFilepathStringDict() class 190 | if self.is_commit_test: 191 | self._add_commit_sha1_to_lists() 192 | self.delta_fp_string_dict.add_commit_sha1( 193 | self.commit_sha1_list 194 | ) # create 'commits' dict key 195 | elif self.is_branch_test: 196 | branch_list = [self.compare_branch_name, self.current_branch_name] 197 | self.delta_fp_string_dict.add_branches( 198 | branch_list 199 | ) # create 'branches' dict key 200 | self.delta_fp_string_dict.add_added_filepaths( 201 | self.added_ufo_file_list 202 | ) # create 'added' dict key 203 | self.delta_fp_string_dict.add_deleted_filepaths( 204 | self.deleted_ufo_file_list 205 | ) # create 'deleted' dict key 206 | self.delta_fp_string_dict.add_modified_filepaths( 207 | self.modified_ufo_file_list 208 | ) # create 'modified' dict key 209 | 210 | def _get_delta_text_string(self): 211 | """ 212 | Generates plain text string format for delta subcommand reports. 213 | :return: (string) plain text string formatted Python string 214 | intended for standard output stream 215 | """ 216 | textstring = "" 217 | if ( 218 | self.is_commit_test is True 219 | ): # include commits if this is an analysis of commit history 220 | # Write SHA1 commits under examination 221 | if len(self.delta_fp_string_dict.delta_dict["commits"]) > 0: 222 | textstring += ( 223 | os.linesep + "Commit history SHA1 for this analysis:" + os.linesep 224 | ) 225 | for sha1_commit in self.delta_fp_string_dict.delta_dict["commits"]: 226 | textstring += " " + sha1_commit + os.linesep 227 | textstring += os.linesep 228 | elif ( 229 | self.is_branch_test is True 230 | ): # include branches if this is a branch v branch analysis 231 | if len(self.delta_fp_string_dict.delta_dict["branches"]) > 0: 232 | textstring += os.linesep + "Branches under analysis:" + os.linesep 233 | for branch in self.delta_fp_string_dict.delta_dict["branches"]: 234 | textstring += " " + branch + os.linesep 235 | textstring += os.linesep 236 | 237 | # include added files 238 | if len(self.delta_fp_string_dict.delta_dict["added"]) > 0: 239 | for added_file in self.delta_fp_string_dict.delta_dict["added"]: 240 | add_append_string = "[A]:" + added_file + os.linesep 241 | textstring += add_append_string 242 | # include deleted files 243 | if len(self.delta_fp_string_dict.delta_dict["deleted"]) > 0: 244 | for deleted_file in self.delta_fp_string_dict.delta_dict["deleted"]: 245 | del_append_string = "[D]:" + deleted_file + os.linesep 246 | textstring += del_append_string 247 | # include modified files 248 | if len(self.delta_fp_string_dict.delta_dict["modified"]) > 0: 249 | for modified_file in self.delta_fp_string_dict.delta_dict["modified"]: 250 | mod_append_string = "[M]:" + modified_file + os.linesep 251 | textstring += mod_append_string 252 | 253 | return textstring 254 | 255 | def _get_delta_json_string(self): 256 | """ 257 | Generates JSON format for deltajson subcommand reports. 258 | :return: (string) JSON formatted Python string intended for 259 | standard output stream 260 | """ 261 | return json.dumps(self.delta_fp_string_dict.delta_dict) 262 | 263 | def _get_delta_markdown_string(self): 264 | """ 265 | Generates Markdown format for deltamd subcommand reports. 266 | :return: (string) Markdown formatted Python string intended for 267 | standard output stream 268 | """ 269 | markdown_string = "" 270 | 271 | if self.is_commit_test is True: 272 | if len(self.delta_fp_string_dict.delta_dict["commits"]) > 0: 273 | markdown_string += ( 274 | os.linesep 275 | + "## Commit history SHA1 for this analysis:" 276 | + os.linesep 277 | ) 278 | for sha1_commit in self.delta_fp_string_dict.delta_dict["commits"]: 279 | markdown_string += "- `" + sha1_commit + "`" + os.linesep 280 | markdown_string += os.linesep 281 | elif self.is_branch_test is True: 282 | if len(self.delta_fp_string_dict.delta_dict["branches"]) > 0: 283 | markdown_string += ( 284 | os.linesep + "## Branches under analysis:" + os.linesep 285 | ) 286 | for branch in self.delta_fp_string_dict.delta_dict["branches"]: 287 | markdown_string += "- " + branch + os.linesep 288 | markdown_string += os.linesep 289 | 290 | # Added files block 291 | markdown_string += "## Added Files" + os.linesep 292 | if len(self.delta_fp_string_dict.delta_dict["added"]) > 0: 293 | for added_file in self.delta_fp_string_dict.delta_dict["added"]: 294 | markdown_string += "- " + added_file + os.linesep 295 | else: 296 | markdown_string += "- None" + os.linesep 297 | 298 | # Deleted files block 299 | markdown_string += os.linesep + os.linesep + "## Deleted Files" + os.linesep 300 | if len(self.delta_fp_string_dict.delta_dict["deleted"]) > 0: 301 | for deleted_file in self.delta_fp_string_dict.delta_dict["deleted"]: 302 | markdown_string += "- " + deleted_file + os.linesep 303 | else: 304 | markdown_string += "- None" + os.linesep 305 | 306 | # Modified files block 307 | markdown_string += os.linesep + os.linesep + "## Modified Files" + os.linesep 308 | if len(self.delta_fp_string_dict.delta_dict["modified"]) > 0: 309 | for modified_file in self.delta_fp_string_dict.delta_dict["modified"]: 310 | markdown_string += "- " + modified_file + os.linesep 311 | else: 312 | markdown_string += "- None" + os.linesep 313 | 314 | # Project URL + version footer 315 | markdown_string += ( 316 | os.linesep 317 | + os.linesep 318 | + "---" 319 | + os.linesep 320 | + "[ufodiff](https://github.com/source-foundry/ufodiff) v" 321 | + major_version 322 | + "." 323 | + minor_version 324 | + "." 325 | + patch_version 326 | ) 327 | 328 | return markdown_string 329 | 330 | # PUBLIC METHODS 331 | 332 | def get_stdout_string(self, write_format=None): 333 | """ 334 | Called by app.py module with write_format type that is dependent 335 | upon the user subcommand request 336 | :param write_format: (string) options include 'text', 'json', and 'markdown' 337 | :return: (string) file change report formatted according to write_format parameter 338 | """ 339 | if write_format == "text": 340 | return self._get_delta_text_string() 341 | elif write_format == "json": 342 | return self._get_delta_json_string() 343 | elif write_format == "markdown": 344 | return self._get_delta_markdown_string() 345 | 346 | # TODO: implement glyph only data 347 | # def get_stdout_string_glyph_only(self): 348 | # pass 349 | 350 | # TODO: implement nonglyph only data 351 | # def get_stdout_string_nonglyph_only(self): 352 | # pass 353 | 354 | 355 | class DeltaFilepathStringDict(object): 356 | """ 357 | Object that maintains a Python dictionary of filepaths that meet UFO 358 | spec and any user defined UFO path filters for use in the generation 359 | of standard output strings. 360 | 361 | User defined UFO directory path filter occurs here. 362 | """ 363 | 364 | def __init__(self, ufo_directory_list): 365 | self.delta_dict = {} 366 | self.ufo_directory_list = ufo_directory_list 367 | 368 | def _filter_and_load_lists(self, filepath_list): 369 | the_filepath_list = [] 370 | # no user defined UFO source filters, include all UFO source 371 | if len(self.ufo_directory_list) == 0: 372 | for delta_file in filepath_list: 373 | the_filepath_list.append(delta_file) 374 | # user wants the results to be filtered by specific UFO directory(ies) 375 | else: 376 | for delta_file in filepath_list: 377 | for ufo_directory_filter_path in self.ufo_directory_list: 378 | if ufo_directory_filter_path in delta_file: 379 | the_filepath_list.append(delta_file) 380 | return the_filepath_list 381 | 382 | def add_added_filepaths(self, added_filepath_list): 383 | self.delta_dict["added"] = self._filter_and_load_lists(added_filepath_list) 384 | 385 | def add_deleted_filepaths(self, deleted_filepath_list): 386 | self.delta_dict["deleted"] = self._filter_and_load_lists(deleted_filepath_list) 387 | 388 | def add_modified_filepaths(self, modified_filepath_list): 389 | self.delta_dict["modified"] = self._filter_and_load_lists( 390 | modified_filepath_list 391 | ) 392 | 393 | def add_commit_sha1(self, commit_sha1_list): 394 | self.delta_dict["commits"] = self._filter_and_load_lists(commit_sha1_list) 395 | 396 | def add_branches(self, branch_name_list): 397 | self.delta_dict["branches"] = self._filter_and_load_lists(branch_name_list) 398 | -------------------------------------------------------------------------------- /lib/ufodiff/subcommands/diff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ==================================================== 5 | # Copyright 2018 Christopher Simpkins 6 | # MIT License 7 | # ==================================================== 8 | 9 | import os 10 | 11 | from git import Repo 12 | 13 | from ufodiff.utilities.ufo import Ufo 14 | 15 | 16 | class Diff(object): 17 | """ 18 | Diff class performs git diff on repository with filters for the UFO specification, 19 | cleans diff reports for improved readability and prepends branch names to the report 20 | for branch vs. branch analyses. Supports git diff reports with ANSI color codes and 21 | without ANSI color codes. 22 | 23 | :param gitrepo_path: (string) path to root of git repository 24 | :param color_diff: (boolean) indicator for request for color diff (True) or 25 | uncolored diff (False) 26 | """ 27 | 28 | def __init__(self, gitrepo_path, color_diff=False): 29 | self.gitrepo_path = gitrepo_path # root path for git repository 30 | self.is_color_diff = color_diff # is request for color diff = True 31 | self.repo = Repo(self.gitrepo_path) # GitPython Repo object 32 | self.git = self.repo.git # GitPython Repo.git object 33 | self.ufo = Ufo() # ufodiff.utilities.ufo.Ufo object 34 | self.current_branch = self.git.rev_parse( 35 | "--abbrev-ref", "HEAD" 36 | ) # current git branch automatically detected 37 | 38 | # PRIVATE METHODS 39 | 40 | def _clean_diff_string(self, dirty_diff_string): 41 | """ 42 | 'Cleans' the raw git diff string to improve readability and eliminate data 43 | that was not felt to be warranted in ufodiff text diff reports. 44 | 45 | :param dirty_diff_string: (string) the raw git diff string 46 | :return: (string) the cleaned git diff string 47 | """ 48 | dirty_diffstring_list = dirty_diff_string.split("\n") 49 | clean_diff_string = "" 50 | for a_string in dirty_diffstring_list: 51 | if a_string.startswith("\x1b[1mdiff --git") or a_string.startswith( 52 | "diff --git" 53 | ): 54 | clean_a_string = a_string.replace("diff --git", "") 55 | clean_a_string = clean_a_string.replace(" ", os.linesep) 56 | clean_diff_string += clean_a_string + os.linesep 57 | elif "100644" in a_string: 58 | clean_a_string = a_string.replace("100644", "") 59 | clean_a_string = clean_a_string.replace("mode", "") 60 | clean_diff_string += clean_a_string + os.linesep 61 | else: 62 | clean_diff_string += a_string + os.linesep 63 | # remove two lead lines from text diffs (unnecessary duplication of the two files) 64 | if "---" in clean_diff_string and "+++" in clean_diff_string: 65 | clean_diff_string_list = clean_diff_string.split(os.linesep) 66 | purged_head_paths_diff_string_list = clean_diff_string_list[3:] 67 | # reset the diff string to empty string and start again 68 | clean_diff_string = "" 69 | for line in purged_head_paths_diff_string_list: 70 | clean_diff_string += line + os.linesep 71 | return clean_diff_string 72 | 73 | # PUBLIC METHODS 74 | 75 | def get_diff_string_generator(self, git_user_diff_string): 76 | """ 77 | Creates a Python generator that returns individual diff reports for filepaths 78 | that match UFO spec filters. 79 | 80 | Generator used as the creation of diff string across large numbers of *.glif 81 | file changes can take time to create. 82 | :param git_user_diff_string: (string) the string provided as third argument in 83 | user command (ufodiff diff [arg3]) 84 | :return: (Python generator of strings) iterable list of diff text strings 85 | intended for standard output 86 | """ 87 | # valid UFO files 88 | ufo_file_list = self.ufo.get_valid_file_filterlist_for_diff() 89 | # default is a test between commits, not a test between branches, modified below 90 | is_branch_test = False 91 | 92 | if git_user_diff_string.startswith("commits:"): 93 | commits_list = git_user_diff_string.split(":") 94 | commits_number = commits_list[1] 95 | diff_arg_string = "HEAD~" + commits_number 96 | elif git_user_diff_string.startswith("branch:"): 97 | is_branch_test = True 98 | diff_arg_string = git_user_diff_string[7:] + ".." + self.current_branch 99 | else: 100 | diff_arg_string = git_user_diff_string 101 | 102 | if self.is_color_diff is True: 103 | for ufo_file in ufo_file_list: 104 | dirty_diff_string = self.git.diff( 105 | diff_arg_string, "--minimal", "--color", "--", ufo_file 106 | ) 107 | cleaned_diff_string = self._clean_diff_string(dirty_diff_string) 108 | # eliminates diff filters that did not include identified files 109 | if len(cleaned_diff_string) > 1: 110 | if is_branch_test is True: 111 | cleaned_diff_string = ( 112 | "branch " 113 | + diff_arg_string 114 | + os.linesep 115 | + cleaned_diff_string 116 | ) 117 | yield cleaned_diff_string 118 | else: 119 | for ufo_file in ufo_file_list: 120 | dirty_diff_string = self.git.diff( 121 | diff_arg_string, "--minimal", "--", ufo_file 122 | ) 123 | cleaned_diff_string = self._clean_diff_string(dirty_diff_string) 124 | # eliminates diff filters that did not include identified files 125 | if len(cleaned_diff_string) > 1: 126 | # add branch descriptions to the output from the diff 127 | if is_branch_test is True: 128 | cleaned_diff_string = ( 129 | "branch " 130 | + diff_arg_string 131 | + os.linesep 132 | + cleaned_diff_string 133 | ) 134 | yield cleaned_diff_string 135 | -------------------------------------------------------------------------------- /lib/ufodiff/utilities/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ==================================================== 5 | # Copyright 2017 Christopher Simpkins 6 | # MIT License 7 | # ==================================================== 8 | 9 | import os 10 | 11 | 12 | def dir_exists(dirpath): 13 | """Tests for existence of a directory on the string filepath""" 14 | if os.path.exists(dirpath) and os.path.isdir( 15 | dirpath 16 | ): # test that exists and is a directory 17 | return True 18 | else: 19 | return False 20 | 21 | 22 | def file_exists(filepath): 23 | """Tests for existence of a file on the string filepath""" 24 | if os.path.exists(filepath) and os.path.isfile( 25 | filepath 26 | ): # test that exists and is a file 27 | return True 28 | else: 29 | return False 30 | -------------------------------------------------------------------------------- /lib/ufodiff/utilities/ufo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ==================================================== 5 | # Copyright 2017 Christopher Simpkins 6 | # MIT License 7 | # ==================================================== 8 | 9 | import os 10 | 11 | 12 | class Ufo(object): 13 | """ 14 | Validates filepaths based upon the Unified Font Object source specification 15 | """ 16 | 17 | def __init__(self): 18 | self.acceptable_files = { 19 | "metainfo.plist", 20 | "fontinfo.plist", 21 | "groups.plist", 22 | "kerning.plist", 23 | "features.fea", 24 | "lib.plist", 25 | "contents.plist", 26 | "layercontents.plist", 27 | "layerinfo.plist", 28 | } 29 | 30 | def _is_images_directory_file(self, filepath): 31 | path_list = filepath.split(os.path.sep) 32 | index = 0 33 | for path_part in path_list: 34 | if path_part == "images": 35 | # .ufo directory should be one directory level above images 36 | ufo_test_directory = path_list[index - 1] 37 | # base filename should have .png extension 38 | base_filename = path_list[-1] 39 | if ( 40 | len(base_filename) > 4 41 | and base_filename[-4:] == ".png" 42 | and ufo_test_directory[-4:] == ".ufo" 43 | ): 44 | # .png file in *.ufo/images directory = pass 45 | return True 46 | else: 47 | index += 1 48 | # if iterate through entire path and never satisfy above test conditions, 49 | # test fails 50 | return False 51 | 52 | def _is_data_directory_file(self, filepath): 53 | path_list = filepath.split(os.path.sep) 54 | index = 0 55 | for path_part in path_list: 56 | if path_part == "data": 57 | # .ufo directory should be one level above data 58 | ufo_test_directory = path_list[index - 1] 59 | # test for presence of *.ufo directory one level up 60 | if ufo_test_directory[-4:] == ".ufo": 61 | # any file in *.ufo/data directory = pass 62 | return True 63 | else: 64 | index += 1 65 | # if iterate through entire path and never satisfy above test conditions, 66 | # test fails 67 | return False 68 | 69 | def validate_file(self, filepath): 70 | if ( 71 | os.path.basename(filepath) in self.acceptable_files 72 | or filepath[-5:] == ".glif" 73 | ): 74 | return True 75 | # UFO v3 data directory file test 76 | elif self._is_data_directory_file(filepath) is True: 77 | return True 78 | # UFO v3 images directory file test 79 | elif self._is_images_directory_file(filepath) is True: 80 | return True 81 | else: 82 | return False 83 | 84 | def get_valid_file_filterlist_for_diff(self): 85 | filter_list = [] 86 | # add valid non-glyph file wildcards 87 | for a_file in self.acceptable_files: 88 | filter_string = "*" + a_file 89 | filter_list.append(filter_string) 90 | # add images directory wildcard (added in UFO v3) 91 | filter_list.append(r"*\.ufo/images/*") 92 | # add data directory wildcard (added in UFO v3) 93 | filter_list.append(r"*\.ufo/data/*") 94 | # add glyph file filters 95 | filter_list.append("*.glif") 96 | return filter_list 97 | 98 | def is_nonglyph_file(self, filepath): 99 | if filepath[-6:] == ".plist" or filepath[-4:] == ".fea": 100 | return True 101 | else: 102 | return False 103 | 104 | def is_glyph_file(self, filepath): 105 | if filepath[-5:] == ".glif": 106 | return True 107 | else: 108 | return False 109 | 110 | def is_ufo_version_file(self, filepath): 111 | if os.path.basename(filepath) == "metainfo.plist": 112 | return True 113 | else: 114 | return False 115 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | python3 setup.py sdist bdist_wheel 4 | twine upload dist/ufodiff-0.5.1* -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile 6 | # 7 | commandlines==0.4.1 # via ufodiff (setup.py) 8 | gitdb==4.0.9 # via gitpython 9 | gitpython==3.1.18 # via ufodiff (setup.py) 10 | smmap==5.0.0 # via gitdb 11 | standardstreams==0.2.0 # via ufodiff (setup.py) 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 0 3 | 4 | [metadata] 5 | license_file = docs/LICENSE 6 | 7 | [flake8] 8 | max-line-length = 90 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import re 4 | import sys 5 | from setuptools import setup, find_packages 6 | 7 | 8 | # Use repository Markdown README.md for PyPI long description 9 | try: 10 | with io.open("README.md", encoding="utf-8") as f: 11 | readme = f.read() 12 | except IOError as readme_e: 13 | sys.stderr.write( 14 | "[ERROR] setup.py: Failed to read the README.md file for the long description definition: {}".format( 15 | str(readme_e) 16 | ) 17 | ) 18 | raise readme_e 19 | 20 | 21 | def version_read(): 22 | settings_file = open( 23 | os.path.join(os.path.dirname(__file__), "lib", "ufodiff", "settings.py") 24 | ).read() 25 | major_regex = """major_version\s*?=\s*?["']{1}(\d+)["']{1}""" 26 | minor_regex = """minor_version\s*?=\s*?["']{1}(\d+)["']{1}""" 27 | patch_regex = """patch_version\s*?=\s*?["']{1}(\d+)["']{1}""" 28 | major_match = re.search(major_regex, settings_file) 29 | minor_match = re.search(minor_regex, settings_file) 30 | patch_match = re.search(patch_regex, settings_file) 31 | major_version = major_match.group(1) 32 | minor_version = minor_match.group(1) 33 | patch_version = patch_match.group(1) 34 | if len(major_version) == 0: 35 | major_version = 0 36 | if len(minor_version) == 0: 37 | minor_version = 0 38 | if len(patch_version) == 0: 39 | patch_version = 0 40 | return major_version + "." + minor_version + "." + patch_version 41 | 42 | 43 | setup( 44 | name="ufodiff", 45 | version=version_read(), 46 | description="UFO font source file diff tool", 47 | long_description=readme, 48 | long_description_content_type="text/markdown", 49 | url="https://github.com/source-foundry/ufodiff", 50 | license="MIT license", 51 | author="Christopher Simpkins", 52 | author_email="chris@sourcefoundry.org", 53 | platforms=["any"], 54 | packages=find_packages("lib"), 55 | package_dir={"": "lib"}, 56 | install_requires=["commandlines", "standardstreams", "gitpython"], 57 | entry_points={"console_scripts": ["ufodiff = ufodiff.app:main"],}, 58 | keywords="font, typeface, ufo, diff, source, ttf, otf", 59 | include_package_data=True, 60 | classifiers=[ 61 | "Development Status :: 5 - Production/Stable", 62 | "Natural Language :: English", 63 | "License :: OSI Approved :: MIT License", 64 | "Operating System :: OS Independent", 65 | "Programming Language :: Python", 66 | "Programming Language :: Python :: 2", 67 | "Programming Language :: Python :: 2.7", 68 | "Programming Language :: Python :: 3", 69 | "Programming Language :: Python :: 3.4", 70 | "Programming Language :: Python :: 3.5", 71 | "Programming Language :: Python :: 3.6", 72 | "Topic :: Software Development :: Libraries :: Python Modules", 73 | ], 74 | ) 75 | -------------------------------------------------------------------------------- /test_runner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # test_runner.sh : local testing script 4 | 5 | tox -e py27,py37 -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/source-foundry/ufodiff/548a1c427322a05ba9b592d62f6f10eb565029d9/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_delta.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | import os 6 | import pytest 7 | import json 8 | 9 | from git import Repo 10 | 11 | from ufodiff.subcommands.delta import Delta, DeltaFilepathStringDict 12 | 13 | # creates a temporary new git branch (testing_branch) for testing 14 | def make_testing_branch(): 15 | repo = Repo(".") 16 | gitobj = repo.git 17 | # create 'test' branch if it doesn't exist so that it can be used for tests in this module 18 | git_branch_string = gitobj.branch() 19 | git_branch_list = git_branch_string.split("\n") 20 | clean_branch_list = [] 21 | for branch in git_branch_list: 22 | branch = branch.replace("*", "") 23 | branch = branch.replace(" ", "") 24 | clean_branch_list.append(branch) 25 | if "testing_branch" in clean_branch_list: 26 | pass 27 | else: 28 | gitobj.branch("testing_branch") 29 | 30 | 31 | # deletes the temporary new git branch (testing_branch) for testing 32 | def delete_testing_branch(): 33 | repo = Repo(".") 34 | gitobj = repo.git 35 | # create 'test' branch if it doesn't exist so that it can be used for tests in this module 36 | git_branch_string = gitobj.branch() 37 | git_branch_list = git_branch_string.split("\n") 38 | clean_branch_list = [] 39 | for branch in git_branch_list: 40 | branch = branch.replace("*", "") 41 | branch = branch.replace(" ", "") 42 | clean_branch_list.append(branch) 43 | if "testing_branch" in clean_branch_list: 44 | gitobj.branch("-d", "testing_branch") 45 | 46 | 47 | def get_mock_added_list(): 48 | added_list = [] 49 | added_list.append(os.path.join("source", "Test-Regular.ufo", "metainfo.plist")) 50 | added_list.append(os.path.join("source", "Test-Italic.ufo", "metainfo.plist")) 51 | added_list.append("README.md") 52 | return added_list 53 | 54 | 55 | def get_mock_deleted_list(): 56 | deleted_list = [] 57 | deleted_list.append(os.path.join("source", "Test-Regular.ufo", "fontinfo.plist")) 58 | deleted_list.append(os.path.join("source", "Test-Italic.ufo", "fontinfo.plist")) 59 | deleted_list.append("CHANGELOG.md") 60 | return deleted_list 61 | 62 | 63 | def get_mock_modified_list(): 64 | modified_list = [] 65 | modified_list.append(os.path.join("source", "Test-Regular.ufo", "features.fea")) 66 | modified_list.append(os.path.join("source", "Test-Italic.ufo", "features.fea")) 67 | modified_list.append("CONTRIBUTING.md") 68 | return modified_list 69 | 70 | 71 | # /////////////////////////////////////////////////////// 72 | # 73 | # Delta class tests 74 | # 75 | # /////////////////////////////////////////////////////// 76 | 77 | 78 | def test_ufodiff_delta_class_instantiation_commit(): 79 | try: 80 | deltaobj = Delta(".", [], is_commit_test=True, commit_number="2") 81 | assert deltaobj.is_commit_test is True 82 | assert deltaobj.is_branch_test is False 83 | assert deltaobj.commit_number == "2" 84 | assert deltaobj.compare_branch_name is None 85 | assert len(deltaobj.ufo_directory_list) == 0 86 | except Exception as e: 87 | pytest.fail(e) 88 | 89 | 90 | def test_ufodiff_delta_class_instantiation_branch(): 91 | make_testing_branch() 92 | 93 | try: 94 | deltaobj = Delta( 95 | ".", [], is_branch_test=True, compare_branch_name="testing_branch" 96 | ) 97 | assert deltaobj.is_commit_test is False 98 | assert deltaobj.is_branch_test is True 99 | assert deltaobj.compare_branch_name == "testing_branch" 100 | assert deltaobj.commit_number == "0" 101 | assert len(deltaobj.ufo_directory_list) == 0 102 | except Exception as e: 103 | delete_testing_branch() 104 | pytest.fail(e) 105 | 106 | delete_testing_branch() 107 | 108 | 109 | def test_ufodiff_delta_class_instantiation_commit_with_ufo_filter(): 110 | try: 111 | deltaobj = Delta( 112 | ".", ["Font-Regular.ufo"], is_commit_test=True, commit_number="2" 113 | ) 114 | assert deltaobj.is_commit_test is True 115 | assert deltaobj.is_branch_test is False 116 | assert deltaobj.commit_number == "2" 117 | assert deltaobj.compare_branch_name is None 118 | assert len(deltaobj.ufo_directory_list) == 1 119 | assert deltaobj.ufo_directory_list[0] == "Font-Regular.ufo" 120 | except Exception as e: 121 | pytest.fail(e) 122 | 123 | 124 | def test_ufodiff_delta_class_instantiation_branch_with_ufo_filter(): 125 | make_testing_branch() 126 | 127 | try: 128 | deltaobj = Delta( 129 | ".", 130 | ["Font-Regular.ufo"], 131 | is_branch_test=True, 132 | compare_branch_name="testing_branch", 133 | ) 134 | assert deltaobj.is_commit_test is False 135 | assert deltaobj.is_branch_test is True 136 | assert deltaobj.compare_branch_name == "testing_branch" 137 | assert deltaobj.commit_number == "0" 138 | assert len(deltaobj.ufo_directory_list) == 1 139 | assert deltaobj.ufo_directory_list[0] == "Font-Regular.ufo" 140 | except Exception as e: 141 | delete_testing_branch() 142 | pytest.fail(e) 143 | 144 | delete_testing_branch() 145 | 146 | 147 | def test_ufodiff_delta_add_commit_sha1_method(): 148 | deltaobj = Delta(".", ["Font-Regular.ufo"], is_commit_test=True, commit_number="1") 149 | # clear sha1 list because defined on instantiation 150 | deltaobj.commit_sha1_list = [] 151 | assert len(deltaobj.commit_sha1_list) == 0 152 | deltaobj._add_commit_sha1_to_lists() 153 | assert len(deltaobj.commit_sha1_list) == 1 154 | 155 | 156 | def test_ufodiff_delta_validate_ufo_load_dict_method_commit_correct_lists_dicts(): 157 | deltaobj = Delta(".", [], is_commit_test=True, commit_number="1") 158 | # clear lists that were created on instantiation of the object for testing 159 | deltaobj.added_ufo_file_list = [] 160 | deltaobj.deleted_ufo_file_list = [] 161 | deltaobj.modified_ufo_file_list = [] 162 | 163 | added_testfile_list = get_mock_added_list() 164 | deleted_testfile_list = get_mock_deleted_list() 165 | modified_testfile_list = get_mock_modified_list() 166 | 167 | # execute the method to be tested 168 | deltaobj._validate_ufo_and_load_dict_from_filepath_strings( 169 | added_testfile_list, deleted_testfile_list, modified_testfile_list 170 | ) 171 | 172 | # confirm UFO validations worked 173 | # added 174 | assert ( 175 | os.path.join("source", "Test-Regular.ufo", "metainfo.plist") 176 | in deltaobj.added_ufo_file_list 177 | ) 178 | assert ( 179 | os.path.join("source", "Test-Italic.ufo", "metainfo.plist") 180 | in deltaobj.added_ufo_file_list 181 | ) 182 | assert ("README.md" in deltaobj.added_ufo_file_list) is False 183 | 184 | # deleted 185 | assert ( 186 | os.path.join("source", "Test-Regular.ufo", "fontinfo.plist") 187 | in deltaobj.deleted_ufo_file_list 188 | ) 189 | assert ( 190 | os.path.join("source", "Test-Italic.ufo", "fontinfo.plist") 191 | in deltaobj.deleted_ufo_file_list 192 | ) 193 | assert ("CHANGELOG.md" in deltaobj.deleted_ufo_file_list) is False 194 | 195 | # modified 196 | assert ( 197 | os.path.join("source", "Test-Regular.ufo", "features.fea") 198 | in deltaobj.modified_ufo_file_list 199 | ) 200 | assert ( 201 | os.path.join("source", "Test-Italic.ufo", "features.fea") 202 | in deltaobj.modified_ufo_file_list 203 | ) 204 | assert ("CONTRIBUTING.md" in deltaobj.modified_ufo_file_list) is False 205 | 206 | # confirm dict keys hold correct file paths 207 | # added 208 | assert ( 209 | os.path.join("source", "Test-Regular.ufo", "metainfo.plist") 210 | in deltaobj.delta_fp_string_dict.delta_dict["added"] 211 | ) 212 | assert ( 213 | os.path.join("source", "Test-Italic.ufo", "metainfo.plist") 214 | in deltaobj.delta_fp_string_dict.delta_dict["added"] 215 | ) 216 | assert ("README.md" in deltaobj.delta_fp_string_dict.delta_dict["added"]) is False 217 | 218 | # deleted 219 | assert ( 220 | os.path.join("source", "Test-Regular.ufo", "fontinfo.plist") 221 | in deltaobj.delta_fp_string_dict.delta_dict["deleted"] 222 | ) 223 | assert ( 224 | os.path.join("source", "Test-Italic.ufo", "fontinfo.plist") 225 | in deltaobj.delta_fp_string_dict.delta_dict["deleted"] 226 | ) 227 | assert ( 228 | "CHANGELOG.md" in deltaobj.delta_fp_string_dict.delta_dict["deleted"] 229 | ) is False 230 | 231 | # modified 232 | assert ( 233 | os.path.join("source", "Test-Regular.ufo", "features.fea") 234 | in deltaobj.delta_fp_string_dict.delta_dict["modified"] 235 | ) 236 | assert ( 237 | os.path.join("source", "Test-Italic.ufo", "features.fea") 238 | in deltaobj.delta_fp_string_dict.delta_dict["modified"] 239 | ) 240 | assert ( 241 | "CONTRIBUTING.md" in deltaobj.delta_fp_string_dict.delta_dict["modified"] 242 | ) is False 243 | 244 | 245 | def test_ufodiff_delta_validate_ufo_load_dict_method_commit_correct_dict_keys(): 246 | deltaobj = Delta(".", [], is_commit_test=True, commit_number="1") 247 | # clear lists that were created on instantiation of the object for testing 248 | deltaobj.added_ufo_file_list = [] 249 | deltaobj.deleted_ufo_file_list = [] 250 | deltaobj.modified_ufo_file_list = [] 251 | 252 | added_testfile_list = get_mock_added_list() 253 | deleted_testfile_list = get_mock_deleted_list() 254 | modified_testfile_list = get_mock_modified_list() 255 | 256 | # execute the method to be tested 257 | deltaobj._validate_ufo_and_load_dict_from_filepath_strings( 258 | added_testfile_list, deleted_testfile_list, modified_testfile_list 259 | ) 260 | 261 | # confirm delta dictionary class object property properly defined with filepaths 262 | delta_dict = deltaobj.delta_fp_string_dict.delta_dict 263 | 264 | assert "added" in delta_dict.keys() 265 | assert "deleted" in delta_dict.keys() 266 | assert "modified" in delta_dict.keys() 267 | assert "commits" in delta_dict.keys() 268 | assert ("branches" in delta_dict.keys()) is False 269 | 270 | 271 | def test_ufodiff_delta_validate_ufo_load_dict_method_branch_correct_lists_dicts(): 272 | make_testing_branch() 273 | 274 | deltaobj = Delta(".", [], is_branch_test=True, compare_branch_name="testing_branch") 275 | # clear lists that were created on instantiation of the object for testing 276 | deltaobj.added_ufo_file_list = [] 277 | deltaobj.deleted_ufo_file_list = [] 278 | deltaobj.modified_ufo_file_list = [] 279 | 280 | added_testfile_list = get_mock_added_list() 281 | deleted_testfile_list = get_mock_deleted_list() 282 | modified_testfile_list = get_mock_modified_list() 283 | 284 | # execute the method to be tested 285 | deltaobj._validate_ufo_and_load_dict_from_filepath_strings( 286 | added_testfile_list, deleted_testfile_list, modified_testfile_list 287 | ) 288 | 289 | # confirm UFO validations worked 290 | # added 291 | assert ( 292 | os.path.join("source", "Test-Regular.ufo", "metainfo.plist") 293 | in deltaobj.added_ufo_file_list 294 | ) 295 | assert ( 296 | os.path.join("source", "Test-Italic.ufo", "metainfo.plist") 297 | in deltaobj.added_ufo_file_list 298 | ) 299 | assert ("README.md" in deltaobj.added_ufo_file_list) is False 300 | 301 | # deleted 302 | assert ( 303 | os.path.join("source", "Test-Regular.ufo", "fontinfo.plist") 304 | in deltaobj.deleted_ufo_file_list 305 | ) 306 | assert ( 307 | os.path.join("source", "Test-Italic.ufo", "fontinfo.plist") 308 | in deltaobj.deleted_ufo_file_list 309 | ) 310 | assert ("CHANGELOG.md" in deltaobj.deleted_ufo_file_list) is False 311 | 312 | # modified 313 | assert ( 314 | os.path.join("source", "Test-Regular.ufo", "features.fea") 315 | in deltaobj.modified_ufo_file_list 316 | ) 317 | assert ( 318 | os.path.join("source", "Test-Italic.ufo", "features.fea") 319 | in deltaobj.modified_ufo_file_list 320 | ) 321 | assert ("CONTRIBUTING.md" in deltaobj.modified_ufo_file_list) is False 322 | 323 | # confirm dict keys hold correct file paths 324 | # added 325 | assert ( 326 | os.path.join("source", "Test-Regular.ufo", "metainfo.plist") 327 | in deltaobj.delta_fp_string_dict.delta_dict["added"] 328 | ) 329 | assert ( 330 | os.path.join("source", "Test-Italic.ufo", "metainfo.plist") 331 | in deltaobj.delta_fp_string_dict.delta_dict["added"] 332 | ) 333 | assert ("README.md" in deltaobj.delta_fp_string_dict.delta_dict["added"]) is False 334 | 335 | # deleted 336 | assert ( 337 | os.path.join("source", "Test-Regular.ufo", "fontinfo.plist") 338 | in deltaobj.delta_fp_string_dict.delta_dict["deleted"] 339 | ) 340 | assert ( 341 | os.path.join("source", "Test-Italic.ufo", "fontinfo.plist") 342 | in deltaobj.delta_fp_string_dict.delta_dict["deleted"] 343 | ) 344 | assert ( 345 | "CHANGELOG.md" in deltaobj.delta_fp_string_dict.delta_dict["deleted"] 346 | ) is False 347 | 348 | # modified 349 | assert ( 350 | os.path.join("source", "Test-Regular.ufo", "features.fea") 351 | in deltaobj.delta_fp_string_dict.delta_dict["modified"] 352 | ) 353 | assert ( 354 | os.path.join("source", "Test-Italic.ufo", "features.fea") 355 | in deltaobj.delta_fp_string_dict.delta_dict["modified"] 356 | ) 357 | assert ( 358 | "CONTRIBUTING.md" in deltaobj.delta_fp_string_dict.delta_dict["modified"] 359 | ) is False 360 | 361 | delete_testing_branch() 362 | 363 | 364 | def test_ufodiff_delta_validate_ufo_load_dict_method_branch_correct_dict_keys(): 365 | make_testing_branch() 366 | 367 | deltaobj = Delta(".", [], is_branch_test=True, compare_branch_name="testing_branch") 368 | # clear lists that were created on instantiation of the object for testing 369 | deltaobj.added_ufo_file_list = [] 370 | deltaobj.deleted_ufo_file_list = [] 371 | deltaobj.modified_ufo_file_list = [] 372 | 373 | added_testfile_list = get_mock_added_list() 374 | deleted_testfile_list = get_mock_deleted_list() 375 | modified_testfile_list = get_mock_modified_list() 376 | 377 | # execute the method to be tested 378 | deltaobj._validate_ufo_and_load_dict_from_filepath_strings( 379 | added_testfile_list, deleted_testfile_list, modified_testfile_list 380 | ) 381 | 382 | # confirm delta dictionary class object property properly defined with filepaths 383 | delta_dict = deltaobj.delta_fp_string_dict.delta_dict 384 | 385 | assert "added" in delta_dict.keys() 386 | assert "deleted" in delta_dict.keys() 387 | assert "modified" in delta_dict.keys() 388 | assert "branches" in delta_dict.keys() 389 | assert ("commits" in delta_dict.keys()) is False 390 | 391 | delete_testing_branch() 392 | 393 | 394 | def test_ufodiff_delta_get_delta_text_string_method_commit(): 395 | deltaobj = Delta(".", [], is_commit_test=True, commit_number="1") 396 | 397 | added_testfile_list = get_mock_added_list() 398 | deleted_testfile_list = get_mock_deleted_list() 399 | modified_testfile_list = get_mock_modified_list() 400 | 401 | # execute the method to be tested 402 | deltaobj._validate_ufo_and_load_dict_from_filepath_strings( 403 | added_testfile_list, deleted_testfile_list, modified_testfile_list 404 | ) 405 | 406 | response_string = deltaobj.get_stdout_string(write_format="text") 407 | # assert len(response_string) > 0 408 | assert "Commit history SHA1 for this analysis:" in response_string 409 | 410 | 411 | def test_ufodiff_delta_get_delta_text_string_method_branch(): 412 | make_testing_branch() 413 | 414 | deltaobj = Delta(".", [], is_branch_test=True, compare_branch_name="testing_branch") 415 | 416 | added_testfile_list = get_mock_added_list() 417 | deleted_testfile_list = get_mock_deleted_list() 418 | modified_testfile_list = get_mock_modified_list() 419 | 420 | # execute the method to be tested 421 | deltaobj._validate_ufo_and_load_dict_from_filepath_strings( 422 | added_testfile_list, deleted_testfile_list, modified_testfile_list 423 | ) 424 | 425 | response_string = deltaobj.get_stdout_string(write_format="text") 426 | # assert len(response_string) > 0 427 | assert "Branches under analysis:" in response_string 428 | 429 | delete_testing_branch() 430 | 431 | 432 | def test_ufodiff_delta_get_delta_json_string_method_commit(): 433 | deltaobj = Delta(".", [], is_commit_test=True, commit_number="1") 434 | 435 | added_testfile_list = get_mock_added_list() 436 | deleted_testfile_list = get_mock_deleted_list() 437 | modified_testfile_list = get_mock_modified_list() 438 | 439 | # execute the method to be tested 440 | deltaobj._validate_ufo_and_load_dict_from_filepath_strings( 441 | added_testfile_list, deleted_testfile_list, modified_testfile_list 442 | ) 443 | 444 | response_string = deltaobj.get_stdout_string(write_format="json") 445 | 446 | json_obj = json.loads(response_string) 447 | assert len(response_string) > 0 448 | assert len(json_obj["commits"]) > 0 449 | 450 | # added 451 | assert ( 452 | os.path.join("source", "Test-Regular.ufo", "metainfo.plist") 453 | in json_obj["added"] 454 | ) 455 | assert ( 456 | os.path.join("source", "Test-Italic.ufo", "metainfo.plist") in json_obj["added"] 457 | ) 458 | assert ("README.md" in json_obj["added"]) is False 459 | 460 | # deleted 461 | assert ( 462 | os.path.join("source", "Test-Regular.ufo", "fontinfo.plist") 463 | in json_obj["deleted"] 464 | ) 465 | assert ( 466 | os.path.join("source", "Test-Italic.ufo", "fontinfo.plist") 467 | in json_obj["deleted"] 468 | ) 469 | assert ("CHANGELOG.md" in json_obj["deleted"]) is False 470 | 471 | # modified 472 | assert ( 473 | os.path.join("source", "Test-Regular.ufo", "features.fea") 474 | in json_obj["modified"] 475 | ) 476 | assert ( 477 | os.path.join("source", "Test-Italic.ufo", "features.fea") 478 | in json_obj["modified"] 479 | ) 480 | assert ("CONTRIBUTING.md" in json_obj["modified"]) is False 481 | 482 | 483 | def test_ufodiff_delta_get_delta_json_string_method_branch(): 484 | make_testing_branch() 485 | 486 | deltaobj = Delta(".", [], is_branch_test=True, compare_branch_name="testing_branch") 487 | 488 | added_testfile_list = get_mock_added_list() 489 | deleted_testfile_list = get_mock_deleted_list() 490 | modified_testfile_list = get_mock_modified_list() 491 | 492 | # execute the method to be tested 493 | deltaobj._validate_ufo_and_load_dict_from_filepath_strings( 494 | added_testfile_list, deleted_testfile_list, modified_testfile_list 495 | ) 496 | 497 | response_string = deltaobj.get_stdout_string(write_format="json") 498 | 499 | json_obj = json.loads(response_string) 500 | assert len(response_string) > 0 501 | assert len(json_obj["branches"]) > 0 502 | 503 | # added 504 | assert ( 505 | os.path.join("source", "Test-Regular.ufo", "metainfo.plist") 506 | in json_obj["added"] 507 | ) 508 | assert ( 509 | os.path.join("source", "Test-Italic.ufo", "metainfo.plist") in json_obj["added"] 510 | ) 511 | assert ("README.md" in json_obj["added"]) is False 512 | 513 | # deleted 514 | assert ( 515 | os.path.join("source", "Test-Regular.ufo", "fontinfo.plist") 516 | in json_obj["deleted"] 517 | ) 518 | assert ( 519 | os.path.join("source", "Test-Italic.ufo", "fontinfo.plist") 520 | in json_obj["deleted"] 521 | ) 522 | assert ("CHANGELOG.md" in json_obj["deleted"]) is False 523 | 524 | # modified 525 | assert ( 526 | os.path.join("source", "Test-Regular.ufo", "features.fea") 527 | in json_obj["modified"] 528 | ) 529 | assert ( 530 | os.path.join("source", "Test-Italic.ufo", "features.fea") 531 | in json_obj["modified"] 532 | ) 533 | assert ("CONTRIBUTING.md" in json_obj["modified"]) is False 534 | 535 | delete_testing_branch() 536 | 537 | 538 | def test_ufodiff_delta_get_delta_markdown_string_method_commit(): 539 | deltaobj = Delta(".", [], is_commit_test=True, commit_number="1") 540 | 541 | added_testfile_list = get_mock_added_list() 542 | deleted_testfile_list = get_mock_deleted_list() 543 | modified_testfile_list = get_mock_modified_list() 544 | 545 | # execute the method to be tested 546 | deltaobj._validate_ufo_and_load_dict_from_filepath_strings( 547 | added_testfile_list, deleted_testfile_list, modified_testfile_list 548 | ) 549 | 550 | response_string = deltaobj.get_stdout_string(write_format="markdown") 551 | 552 | assert len(response_string) > 0 553 | 554 | # added 555 | assert ( 556 | os.path.join("source", "Test-Regular.ufo", "metainfo.plist") in response_string 557 | ) 558 | assert ( 559 | os.path.join("source", "Test-Italic.ufo", "metainfo.plist") in response_string 560 | ) 561 | assert ("README.md" in response_string) is False 562 | 563 | # deleted 564 | assert ( 565 | os.path.join("source", "Test-Regular.ufo", "fontinfo.plist") in response_string 566 | ) 567 | assert ( 568 | os.path.join("source", "Test-Italic.ufo", "fontinfo.plist") in response_string 569 | ) 570 | assert ("CHANGELOG.md" in response_string) is False 571 | 572 | # modified 573 | assert os.path.join("source", "Test-Regular.ufo", "features.fea") in response_string 574 | assert os.path.join("source", "Test-Italic.ufo", "features.fea") in response_string 575 | assert ("CONTRIBUTING.md" in response_string) is False 576 | 577 | 578 | def test_ufodiff_delta_get_delta_markdown_string_method_branch(): 579 | make_testing_branch() 580 | 581 | deltaobj = Delta(".", [], is_branch_test=True, compare_branch_name="testing_branch") 582 | 583 | added_testfile_list = get_mock_added_list() 584 | deleted_testfile_list = get_mock_deleted_list() 585 | modified_testfile_list = get_mock_modified_list() 586 | 587 | # execute the method to be tested 588 | deltaobj._validate_ufo_and_load_dict_from_filepath_strings( 589 | added_testfile_list, deleted_testfile_list, modified_testfile_list 590 | ) 591 | 592 | response_string = deltaobj.get_stdout_string(write_format="markdown") 593 | 594 | assert len(response_string) > 0 595 | 596 | # added 597 | assert ( 598 | os.path.join("source", "Test-Regular.ufo", "metainfo.plist") in response_string 599 | ) 600 | assert ( 601 | os.path.join("source", "Test-Italic.ufo", "metainfo.plist") in response_string 602 | ) 603 | assert ("README.md" in response_string) is False 604 | 605 | # deleted 606 | assert ( 607 | os.path.join("source", "Test-Regular.ufo", "fontinfo.plist") in response_string 608 | ) 609 | assert ( 610 | os.path.join("source", "Test-Italic.ufo", "fontinfo.plist") in response_string 611 | ) 612 | assert ("CHANGELOG.md" in response_string) is False 613 | 614 | # modified 615 | assert os.path.join("source", "Test-Regular.ufo", "features.fea") in response_string 616 | assert os.path.join("source", "Test-Italic.ufo", "features.fea") in response_string 617 | assert ("CONTRIBUTING.md" in response_string) is False 618 | 619 | delete_testing_branch() 620 | 621 | 622 | # /////////////////////////////////////////////////////// 623 | # 624 | # DeltaFilepathStringDict class tests 625 | # 626 | # /////////////////////////////////////////////////////// 627 | 628 | 629 | def test_ufodiff_dfpd_instantiation_empty_fp_list(): # user did not specify UFO source filter so list arg empty 630 | dfpd = DeltaFilepathStringDict([]) 631 | assert len(dfpd.ufo_directory_list) == 0 632 | 633 | 634 | def test_ufodiff_dfpd_instantiation_nonempty_fp_list(): # user did specify UFO source filter so list arg not empty 635 | filepath_list = ["Font-Regular.ufo", "Font-Italic.ufo"] 636 | dfpd = DeltaFilepathStringDict(filepath_list) 637 | assert len(dfpd.ufo_directory_list) == 2 638 | assert "Font-Regular.ufo" in dfpd.ufo_directory_list 639 | assert "Font-Italic.ufo" in dfpd.ufo_directory_list 640 | 641 | 642 | def test_ufodiff_dfpd_add_added_fp_method(): 643 | dfpd = DeltaFilepathStringDict([]) 644 | dfpd2 = DeltaFilepathStringDict(["Test-Regular.ufo"]) 645 | dfpd3 = DeltaFilepathStringDict(["Test-Regular.ufo"]) 646 | added_list = [os.path.join("source", "NotPath-Regular.ufo", "fontinfo.plist")] 647 | added_filterpath_list = [ 648 | os.path.join("source", "Test-Regular.ufo", "metainfo.plist") 649 | ] 650 | 651 | # this one tests when no user requested source filter applied 652 | dfpd.add_added_filepaths(added_list) 653 | # this one tests when file path is not on source filter filepath, should remain empty 654 | dfpd2.add_added_filepaths(added_list) 655 | # this one tests when file path is on source filter path, should include in the dictionary 656 | dfpd3.add_added_filepaths(added_filterpath_list) 657 | 658 | assert len(dfpd.delta_dict["added"]) == 1 659 | assert dfpd.delta_dict["added"][0] == os.path.join( 660 | "source", "NotPath-Regular.ufo", "fontinfo.plist" 661 | ) 662 | 663 | assert len(dfpd2.delta_dict["added"]) == 0 664 | 665 | assert len(dfpd3.delta_dict["added"]) == 1 666 | assert dfpd3.delta_dict["added"][0] == os.path.join( 667 | "source", "Test-Regular.ufo", "metainfo.plist" 668 | ) 669 | 670 | 671 | def test_ufodiff_dfpd_add_deleted_fp_method(): 672 | dfpd = DeltaFilepathStringDict([]) 673 | dfpd2 = DeltaFilepathStringDict(["Test-Regular.ufo"]) 674 | dfpd3 = DeltaFilepathStringDict(["Test-Regular.ufo"]) 675 | deleted_list = [os.path.join("source", "NotPath-Regular.ufo", "fontinfo.plist")] 676 | deleted_filterpath_list = [ 677 | os.path.join("source", "Test-Regular.ufo", "metainfo.plist") 678 | ] 679 | 680 | # this one tests when no user requested source filter applied 681 | dfpd.add_deleted_filepaths(deleted_list) 682 | # this one tests when file path is not on source filter filepath, should remain empty 683 | dfpd2.add_deleted_filepaths(deleted_list) 684 | # this one tests when file path is on source filter path, should include in the dictionary 685 | dfpd3.add_deleted_filepaths(deleted_filterpath_list) 686 | 687 | assert len(dfpd.delta_dict["deleted"]) == 1 688 | assert dfpd.delta_dict["deleted"][0] == os.path.join( 689 | "source", "NotPath-Regular.ufo", "fontinfo.plist" 690 | ) 691 | 692 | assert len(dfpd2.delta_dict["deleted"]) == 0 693 | 694 | assert len(dfpd3.delta_dict["deleted"]) == 1 695 | assert dfpd3.delta_dict["deleted"][0] == os.path.join( 696 | "source", "Test-Regular.ufo", "metainfo.plist" 697 | ) 698 | 699 | 700 | def test_ufodiff_dfpd_add_modified_fp_method(): 701 | dfpd = DeltaFilepathStringDict([]) 702 | dfpd2 = DeltaFilepathStringDict(["Test-Regular.ufo"]) 703 | dfpd3 = DeltaFilepathStringDict(["Test-Regular.ufo"]) 704 | modified_list = [os.path.join("source", "NotPath-Regular.ufo", "fontinfo.plist")] 705 | modified_filterpath_list = [ 706 | os.path.join("source", "Test-Regular.ufo", "metainfo.plist") 707 | ] 708 | 709 | # this one tests when no user requested source filter applied 710 | dfpd.add_modified_filepaths(modified_list) 711 | # this one tests when file path is not on source filter filepath, should remain empty 712 | dfpd2.add_modified_filepaths(modified_list) 713 | # this one tests when file path is on source filter path, should include in the dictionary 714 | dfpd3.add_modified_filepaths(modified_filterpath_list) 715 | 716 | assert len(dfpd.delta_dict["modified"]) == 1 717 | assert dfpd.delta_dict["modified"][0] == os.path.join( 718 | "source", "NotPath-Regular.ufo", "fontinfo.plist" 719 | ) 720 | 721 | assert len(dfpd2.delta_dict["modified"]) == 0 722 | 723 | assert len(dfpd3.delta_dict["modified"]) == 1 724 | assert dfpd3.delta_dict["modified"][0] == os.path.join( 725 | "source", "Test-Regular.ufo", "metainfo.plist" 726 | ) 727 | -------------------------------------------------------------------------------- /tests/test_diff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import pytest 7 | import types 8 | 9 | from git import Repo 10 | 11 | from ufodiff.subcommands.diff import Diff 12 | 13 | test_dirty_diff_string_commits_nocolor = """diff --git a/README.md b/README.md 14 | index c13403487..8e84d1d01 15 | --- a/source/ufo/vfb2ufo/Hack-Regular-PS.ufo/fontinfo.plist 16 | +++ b/source/ufo/vfb2ufo/Hack-Regular-PS.ufo/fontinfo.plist 17 | @@ -7,7 +7,7 @@ 18 | capHeight 19 | 1493 20 | copyright 21 | - Copyright (c) 2015 Christopher Simpkins / Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. 22 | + Copyright (c) 2017 Christopher Simpkins / Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. 23 | descender 24 | -492 25 | familyName""" 26 | 27 | test_dirty_diff_string_commits_color = """\x1b[1mdiff --git a/README.md b/README.md^[[m 28 | \x1b[1mindex 47e8ec3d2..f5436222b 100644^[[m 29 | ^[[1m--- a/README.md^[[m 30 | ^[[1m+++ b/README.md^[[m 31 | ^[[36m@@ -1,6 +1,6 @@^[[m 32 | ^[[m 33 | # Hack^[[m 34 | ^[[31m-### A typeface designed for source code^[[m 35 | ^[[32m+^[[m^[[32m### A typeface designed for source code-test^[[m 36 | ^[[m 37 | ^[[m 39 | ^[[1mdiff --git a/source/ufo/vfb2ufo/Hack-Regular-PS.ufo/fontinfo.plist b/sourc$ 40 | ^[[1mindex c13403487..8e84d1d01 100644^[[m 41 | ^[[1m--- a/source/ufo/vfb2ufo/Hack-Regular-PS.ufo/fontinfo.plist^[[m 42 | ^[[1m+++ b/source/ufo/vfb2ufo/Hack-Regular-PS.ufo/fontinfo.plist^[[m 43 | ^[[36m@@ -7,7 +7,7 @@^[[m 44 | capHeight^[[m 45 | 1493^[[m 46 | copyright^[[m 47 | ^[[31m- Copyright (c) 2015 Christopher Simpkins / Copyright (c) 2003 b$ 48 | ^[[32m+^[[m^[[32m Copyright (c) 2017 Christopher Simpkins / Copyright $ 49 | descender^[[m 50 | -492^[[m 51 | familyName^[[m""" 52 | 53 | 54 | # creates a temporary new git branch (testing_branch) for testing 55 | def make_testing_branch(): 56 | repo = Repo(".") 57 | gitobj = repo.git 58 | # create 'test' branch if it doesn't exist so that it can be used for tests in this module 59 | git_branch_string = gitobj.branch() 60 | git_branch_list = git_branch_string.split("\n") 61 | clean_branch_list = [] 62 | for branch in git_branch_list: 63 | branch = branch.replace("*", "") 64 | branch = branch.replace(" ", "") 65 | clean_branch_list.append(branch) 66 | if "testing_branch" in clean_branch_list: 67 | pass 68 | else: 69 | gitobj.branch("testing_branch") 70 | 71 | 72 | # deletes the temporary new git branch (testing_branch) for testing 73 | def delete_testing_branch(): 74 | repo = Repo(".") 75 | gitobj = repo.git 76 | # create 'test' branch if it doesn't exist so that it can be used for tests in this module 77 | git_branch_string = gitobj.branch() 78 | git_branch_list = git_branch_string.split("\n") 79 | clean_branch_list = [] 80 | for branch in git_branch_list: 81 | branch = branch.replace("*", "") 82 | branch = branch.replace(" ", "") 83 | clean_branch_list.append(branch) 84 | if "testing_branch" in clean_branch_list: 85 | gitobj.branch("-d", "testing_branch") 86 | 87 | 88 | # /////////////////////////////////////////////////////// 89 | # 90 | # Diff class tests 91 | # 92 | # /////////////////////////////////////////////////////// 93 | 94 | 95 | def test_ufodiff_diff_class_instantiation_default(): 96 | diff = Diff(".") 97 | assert diff.is_color_diff is False 98 | assert diff.gitrepo_path == "." 99 | 100 | 101 | def test_ufodiff_diff_class_instantation_color_true(): 102 | diff = Diff(".", color_diff=True) 103 | assert diff.is_color_diff is True 104 | assert diff.gitrepo_path == "." 105 | 106 | 107 | def test_ufodiff_diff_clean_diff_string_method_uncolored_string(): 108 | diff = Diff(".") 109 | cleaned_string = diff._clean_diff_string(test_dirty_diff_string_commits_nocolor) 110 | assert cleaned_string.startswith("diff --git") is False 111 | assert cleaned_string.startswith("index c13403487") is True 112 | 113 | 114 | def test_ufodiff_diff_clean_diff_string_method_colored_string(): 115 | diff = Diff(".") 116 | cleaned_string = diff._clean_diff_string(test_dirty_diff_string_commits_color) 117 | assert cleaned_string.startswith("\x1b[1mdiff") is False 118 | assert cleaned_string.startswith("\x1b[1mindex 47e8ec3d2") is True 119 | 120 | 121 | def test_ufodiff_diff_get_diff_string_generator_method_uncolored_commits(): 122 | diffobj = Diff(".") 123 | test_generator = diffobj.get_diff_string_generator("commits:1") 124 | for thing in test_generator: 125 | pass 126 | assert isinstance(test_generator, types.GeneratorType) 127 | 128 | 129 | def test_ufodiff_diff_get_diff_string_generator_method_colored_commits(): 130 | diffobj = Diff(".", color_diff=True) 131 | test_generator = diffobj.get_diff_string_generator("commits:1") 132 | for thing in test_generator: 133 | pass 134 | assert isinstance(test_generator, types.GeneratorType) 135 | 136 | 137 | def test_ufodiff_diff_get_diff_string_generator_method_uncolored_branch(): 138 | make_testing_branch() 139 | 140 | diffobj = Diff(".") 141 | test_generator = diffobj.get_diff_string_generator("branch:testing_branch") 142 | for thing in test_generator: 143 | pass 144 | assert isinstance(test_generator, types.GeneratorType) 145 | 146 | delete_testing_branch() 147 | 148 | 149 | def test_ufodiff_diff_get_diff_string_generator_method_colored_branch(): 150 | make_testing_branch() 151 | 152 | diffobj = Diff(".", color_diff=True) 153 | test_generator = diffobj.get_diff_string_generator("branch:testing_branch") 154 | for thing in test_generator: 155 | pass 156 | assert isinstance(test_generator, types.GeneratorType) 157 | 158 | delete_testing_branch() 159 | 160 | 161 | def test_ufodiff_diff_get_diff_string_generator_method_uncolored_gitidiom(): 162 | diffobj = Diff(".") 163 | test_generator = diffobj.get_diff_string_generator("HEAD~1") 164 | for thing in test_generator: 165 | pass 166 | assert isinstance(test_generator, types.GeneratorType) 167 | 168 | 169 | def test_ufodiff_diff_get_diff_string_generator_method_colored_gitidiom(): 170 | diffobj = Diff(".", color_diff=True) 171 | test_generator = diffobj.get_diff_string_generator("HEAD~1") 172 | for thing in test_generator: 173 | pass 174 | assert isinstance(test_generator, types.GeneratorType) 175 | -------------------------------------------------------------------------------- /tests/test_main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import pytest 7 | import mock 8 | 9 | from git import Repo 10 | 11 | from ufodiff.app import get_git_root_path 12 | 13 | 14 | # creates a temporary new git branch (testing_branch) for testing 15 | def make_testing_branch(): 16 | repo = Repo(".") 17 | gitobj = repo.git 18 | # create 'test' branch if it doesn't exist so that it can be used for tests in this module 19 | git_branch_string = gitobj.branch() 20 | git_branch_list = git_branch_string.split("\n") 21 | clean_branch_list = [] 22 | for branch in git_branch_list: 23 | branch = branch.replace("*", "") 24 | branch = branch.replace(" ", "") 25 | clean_branch_list.append(branch) 26 | if "testing_branch" in clean_branch_list: 27 | pass 28 | else: 29 | gitobj.branch("testing_branch") 30 | 31 | 32 | # deletes the temporary new git branch (testing_branch) for testing 33 | def delete_testing_branch(): 34 | repo = Repo(".") 35 | gitobj = repo.git 36 | # create 'test' branch if it doesn't exist so that it can be used for tests in this module 37 | git_branch_string = gitobj.branch() 38 | git_branch_list = git_branch_string.split("\n") 39 | clean_branch_list = [] 40 | for branch in git_branch_list: 41 | branch = branch.replace("*", "") 42 | branch = branch.replace(" ", "") 43 | clean_branch_list.append(branch) 44 | if "testing_branch" in clean_branch_list: 45 | gitobj.branch("-d", "testing_branch") 46 | 47 | 48 | # /////////////////////////////////////////////////////// 49 | # 50 | # pytest capsys capture tests 51 | # confirms capture of std output and std error streams 52 | # 53 | # /////////////////////////////////////////////////////// 54 | 55 | 56 | def test_pytest_capsys(capsys): 57 | print("bogus text for a test") 58 | sys.stderr.write("more text for a test") 59 | out, err = capsys.readouterr() 60 | assert out == "bogus text for a test\n" 61 | assert out != "something else" 62 | assert err == "more text for a test" 63 | assert err != "something else" 64 | 65 | 66 | # /////////////////////////////////////////////////////// 67 | # 68 | # Standard output tests for help, usage, version 69 | # 70 | # /////////////////////////////////////////////////////// 71 | 72 | 73 | def test_ufodiff_commandline_shorthelp(capsys): 74 | with pytest.raises(SystemExit) as pytest_wrapped_e: 75 | from ufodiff.app import main 76 | 77 | sys.argv = ["ufodiff", "-h"] 78 | main() 79 | 80 | out, err = capsys.readouterr() 81 | assert ( 82 | out.startswith("====================================================") is True 83 | ) 84 | assert pytest_wrapped_e.type == SystemExit 85 | assert pytest_wrapped_e.value.code == 0 86 | 87 | 88 | def test_ufodiff_commandline_longhelp(capsys): 89 | with pytest.raises(SystemExit) as pytest_wrapped_e: 90 | from ufodiff.app import main 91 | 92 | sys.argv = ["ufodiff", "--help"] 93 | main() 94 | 95 | out, err = capsys.readouterr() 96 | assert ( 97 | out.startswith("====================================================") is True 98 | ) 99 | assert pytest_wrapped_e.type == SystemExit 100 | assert pytest_wrapped_e.value.code == 0 101 | 102 | 103 | def test_ufodiff_commandline_shortversion(capsys): 104 | with pytest.raises(SystemExit) as pytest_wrapped_e: 105 | from ufodiff.app import main 106 | from ufodiff.app import settings 107 | 108 | sys.argv = ["ufodiff", "-v"] 109 | main() 110 | 111 | out, err = capsys.readouterr() 112 | assert out.startswith(settings.VERSION) 113 | assert pytest_wrapped_e.type == SystemExit 114 | assert pytest_wrapped_e.value.code == 0 115 | 116 | 117 | def test_ufodiff_commandline_longversion(capsys): 118 | with pytest.raises(SystemExit) as pytest_wrapped_e: 119 | from ufodiff.app import main 120 | from ufodiff.app import settings 121 | 122 | sys.argv = ["ufodiff", "--version"] 123 | main() 124 | 125 | out, err = capsys.readouterr() 126 | assert out.startswith(settings.VERSION) 127 | assert pytest_wrapped_e.type == SystemExit 128 | assert pytest_wrapped_e.value.code == 0 129 | 130 | 131 | def test_ufodiff_commandline_longusage(capsys): 132 | with pytest.raises(SystemExit) as pytest_wrapped_e: 133 | from ufodiff.app import main 134 | from ufodiff.app import settings 135 | 136 | sys.argv = ["ufodiff", "--usage"] 137 | main() 138 | 139 | out, err = capsys.readouterr() 140 | assert out.startswith(settings.USAGE) 141 | assert pytest_wrapped_e.type == SystemExit 142 | assert pytest_wrapped_e.value.code == 0 143 | 144 | 145 | # /////////////////////////////////////////////////////////////////// 146 | # 147 | # Standard output tests for non-command specific argument validations 148 | # 149 | # /////////////////////////////////////////////////////////////////// 150 | 151 | 152 | def test_ufodiff_commandline_missingargs(capsys): 153 | with pytest.raises(SystemExit) as pytest_wrapped_e: 154 | from ufodiff.app import main 155 | 156 | sys.argv = ["ufodiff"] 157 | main() 158 | 159 | out, err = capsys.readouterr() 160 | assert err.startswith("[ufodiff] ERROR: Please include the appropriate arguments") 161 | assert pytest_wrapped_e.type == SystemExit 162 | assert pytest_wrapped_e.value.code == 1 163 | 164 | 165 | # //////////////////////////////////////////////////////////// 166 | # 167 | # Standard output tests for delta command argument validations 168 | # 169 | # //////////////////////////////////////////////////////////// 170 | 171 | 172 | def test_ufodiff_commandline_delta_missingargs(capsys): 173 | with pytest.raises(SystemExit) as pytest_wrapped_e: 174 | from ufodiff.app import main 175 | 176 | sys.argv = ["ufodiff", "delta"] 177 | main() 178 | 179 | out, err = capsys.readouterr() 180 | assert err.startswith("[ufodiff] ERROR: ") 181 | assert pytest_wrapped_e.type == SystemExit 182 | assert pytest_wrapped_e.value.code == 1 183 | 184 | 185 | def test_ufodiff_commandline_delta_unacceptable_subsub(capsys): 186 | with pytest.raises(SystemExit) as pytest_wrapped_e: 187 | from ufodiff.app import main 188 | 189 | sys.argv = ["ufodiff", "delta", "bogus", "commits:1"] 190 | main() 191 | 192 | out, err = capsys.readouterr() 193 | assert err.startswith("[ufodiff] ERROR:") 194 | assert pytest_wrapped_e.type == SystemExit 195 | assert pytest_wrapped_e.value.code == 1 196 | 197 | 198 | def test_ufodiff_commandline_delta_missing_commits_arg(capsys): 199 | with pytest.raises(SystemExit) as pytest_wrapped_e: 200 | from ufodiff.app import main 201 | 202 | sys.argv = ["ufodiff", "delta", "all", "bogus"] 203 | main() 204 | 205 | out, err = capsys.readouterr() 206 | assert err.startswith("[ufodiff] ERROR:") 207 | assert pytest_wrapped_e.type == SystemExit 208 | assert pytest_wrapped_e.value.code == 1 209 | 210 | 211 | def test_ufodiff_commandline_delta_missing_commits_number(capsys): 212 | with pytest.raises(SystemExit) as pytest_wrapped_e: 213 | from ufodiff.app import main 214 | 215 | sys.argv = ["ufodiff", "delta", "all", "commits:"] 216 | main() 217 | 218 | out, err = capsys.readouterr() 219 | assert err.startswith("[ufodiff] ERROR:") 220 | assert pytest_wrapped_e.type == SystemExit 221 | assert pytest_wrapped_e.value.code == 1 222 | 223 | 224 | def test_ufodiff_commandline_delta_commits_number_notdigit(capsys): 225 | with pytest.raises(SystemExit) as pytest_wrapped_e: 226 | from ufodiff.app import main 227 | 228 | sys.argv = ["ufodiff", "delta", "all", "commits:a"] 229 | main() 230 | 231 | out, err = capsys.readouterr() 232 | assert err.startswith("[ufodiff] ERROR:") 233 | assert pytest_wrapped_e.type == SystemExit 234 | assert pytest_wrapped_e.value.code == 1 235 | 236 | 237 | def test_ufodiff_commandline_delta_missing_branch_name(capsys): 238 | with pytest.raises(SystemExit) as pytest_wrapped_e: 239 | from ufodiff.app import main 240 | 241 | sys.argv = ["ufodiff", "delta", "all", "branch:"] 242 | main() 243 | 244 | out, err = capsys.readouterr() 245 | assert err.startswith("[ufodiff] ERROR:") 246 | assert pytest_wrapped_e.type == SystemExit 247 | assert pytest_wrapped_e.value.code == 1 248 | 249 | 250 | def test_commandline_delta_existing_branch_name(capsys): 251 | make_testing_branch() 252 | 253 | with pytest.raises(SystemExit) as pytest_wrapped_e: 254 | from ufodiff.app import main 255 | 256 | sys.argv = ["ufodiff", "delta", "all", "branch:testing_branch"] 257 | main() 258 | 259 | out, err = capsys.readouterr() 260 | assert pytest_wrapped_e.type == SystemExit 261 | assert pytest_wrapped_e.value.code == 0 262 | 263 | delete_testing_branch() 264 | 265 | 266 | def test_ufodiff_commandline_delta_commits_number_with_ufo_filter(capsys): 267 | with pytest.raises(SystemExit) as pytest_wrapped_e: 268 | from ufodiff.app import main 269 | 270 | sys.argv = ["ufodiff", "delta", "all", "commits:1", "Test-Regular.ufo"] 271 | main() 272 | 273 | out, err = capsys.readouterr() 274 | assert pytest_wrapped_e.type == SystemExit 275 | assert pytest_wrapped_e.value.code == 0 276 | 277 | 278 | def test_ufodiff_commandline_delta_git_root_check_one_level_below(capsys): 279 | the_cwd = os.getcwd() 280 | with pytest.raises(SystemExit) as pytest_wrapped_e: 281 | from ufodiff.app import main 282 | 283 | sys.argv = ["ufodiff", "delta", "all", "commits:1"] 284 | os.chdir(os.path.join(the_cwd, "tests")) 285 | main() 286 | 287 | out, err = capsys.readouterr() 288 | os.chdir(the_cwd) 289 | assert pytest_wrapped_e.type == SystemExit 290 | assert pytest_wrapped_e.value.code == 0 291 | 292 | 293 | def test_ufodiff_commandline_delta_git_root_check_two_levels_below(capsys): 294 | the_cwd = os.getcwd() 295 | with pytest.raises(SystemExit) as pytest_wrapped_e: 296 | from ufodiff.app import main 297 | 298 | sys.argv = ["ufodiff", "delta", "all", "commits:1"] 299 | os.chdir(os.path.join(the_cwd, "tests", "testfiles")) 300 | main() 301 | 302 | out, err = capsys.readouterr() 303 | os.chdir(the_cwd) 304 | assert pytest_wrapped_e.type == SystemExit 305 | assert pytest_wrapped_e.value.code == 0 306 | 307 | 308 | def test_ufodiff_commandline_delta_git_root_check_three_levels_below(capsys): 309 | the_cwd = os.getcwd() 310 | with pytest.raises(SystemExit) as pytest_wrapped_e: 311 | from ufodiff.app import main 312 | 313 | sys.argv = ["ufodiff", "delta", "all", "commits:1"] 314 | os.chdir(os.path.join(the_cwd, "tests", "testfiles", "depth2")) 315 | main() 316 | 317 | out, err = capsys.readouterr() 318 | os.chdir(the_cwd) 319 | assert pytest_wrapped_e.type == SystemExit 320 | assert pytest_wrapped_e.value.code == 0 321 | 322 | 323 | # should fail at depth of four directory levels deep in the repository 324 | def test_ufodiff_commandline_delta_git_root_check_four_levels_below(capsys): 325 | the_cwd = os.getcwd() 326 | with pytest.raises(SystemExit) as pytest_wrapped_e: 327 | from ufodiff.app import main 328 | 329 | sys.argv = ["ufodiff", "delta", "all", "commits:1"] 330 | os.chdir(os.path.join(the_cwd, "tests", "testfiles", "depth2", "depth3")) 331 | main() 332 | 333 | out, err = capsys.readouterr() 334 | os.chdir(the_cwd) 335 | assert pytest_wrapped_e.type == SystemExit 336 | assert pytest_wrapped_e.value.code == 1 337 | 338 | 339 | def test_ufodiff_commandline_delta_commits_number_is_zero(capsys): 340 | with pytest.raises(SystemExit) as pytest_wrapped_e: 341 | from ufodiff.app import main 342 | 343 | sys.argv = ["ufodiff", "delta", "all", "commits:0"] 344 | main() 345 | 346 | out, err = capsys.readouterr() 347 | assert err.startswith("[ufodiff] ERROR:") 348 | assert pytest_wrapped_e.type == SystemExit 349 | assert pytest_wrapped_e.value.code == 1 350 | 351 | 352 | def test_ufodiff_commandline_delta_commits_number_is_lessthan_zero(capsys): 353 | with pytest.raises(SystemExit) as pytest_wrapped_e: 354 | from ufodiff.app import main 355 | 356 | sys.argv = ["ufodiff", "delta", "all", "commits:-1"] 357 | main() 358 | 359 | out, err = capsys.readouterr() 360 | assert err.startswith("[ufodiff] ERROR:") 361 | assert pytest_wrapped_e.type == SystemExit 362 | assert pytest_wrapped_e.value.code == 1 363 | 364 | 365 | ## SUCCESS COMMANDS FOR DELTAJSON and DELTAMD 366 | 367 | 368 | def test_ufodiff_commandline_deltajson_exit_success(capsys): 369 | with pytest.raises(SystemExit) as pytest_wrapped_e: 370 | from ufodiff.app import main 371 | 372 | sys.argv = ["ufodiff", "deltajson", "all", "commits:2"] 373 | main() 374 | 375 | out, err = capsys.readouterr() 376 | assert pytest_wrapped_e.type == SystemExit 377 | assert pytest_wrapped_e.value.code == 0 378 | 379 | 380 | def test_ufodiff_commandline_deltamd_exit_success(capsys): 381 | with pytest.raises(SystemExit) as pytest_wrapped_e: 382 | from ufodiff.app import main 383 | 384 | sys.argv = ["ufodiff", "deltamd", "all", "commits:2"] 385 | main() 386 | 387 | out, err = capsys.readouterr() 388 | assert pytest_wrapped_e.type == SystemExit 389 | assert pytest_wrapped_e.value.code == 0 390 | 391 | 392 | # //////////////////////////////////////////////////////////// 393 | # 394 | # Standard output tests for diff + diffnc command argument validations 395 | # 396 | # //////////////////////////////////////////////////////////// 397 | 398 | 399 | def test_ufodiff_commandline_diff_missingargs(capsys): 400 | with pytest.raises(SystemExit) as pytest_wrapped_e: 401 | from ufodiff.app import main 402 | 403 | sys.argv = ["ufodiff", "diff"] 404 | main() 405 | 406 | out, err = capsys.readouterr() 407 | assert err.startswith("[ufodiff] ERROR: ") 408 | assert pytest_wrapped_e.type == SystemExit 409 | assert pytest_wrapped_e.value.code == 1 410 | 411 | 412 | def test_ufodiff_commandline_diffnc_missingargs(capsys): 413 | with pytest.raises(SystemExit) as pytest_wrapped_e: 414 | from ufodiff.app import main 415 | 416 | sys.argv = ["ufodiff", "diffnc"] 417 | main() 418 | 419 | out, err = capsys.readouterr() 420 | assert err.startswith("[ufodiff] ERROR: ") 421 | assert pytest_wrapped_e.type == SystemExit 422 | assert pytest_wrapped_e.value.code == 1 423 | 424 | 425 | def test_ufodiff_commandline_diff_missing_commits(capsys): 426 | with pytest.raises(SystemExit) as pytest_wrapped_e: 427 | from ufodiff.app import main 428 | 429 | sys.argv = ["ufodiff", "diff", "commits:"] 430 | main() 431 | 432 | out, err = capsys.readouterr() 433 | assert err.startswith("[ufodiff] ERROR:") 434 | assert pytest_wrapped_e.type == SystemExit 435 | assert pytest_wrapped_e.value.code == 1 436 | 437 | 438 | def test_ufodiff_commandline_diffnc_missing_commits(capsys): 439 | with pytest.raises(SystemExit) as pytest_wrapped_e: 440 | from ufodiff.app import main 441 | 442 | sys.argv = ["ufodiff", "diffnc", "commits:"] 443 | main() 444 | 445 | out, err = capsys.readouterr() 446 | assert err.startswith("[ufodiff] ERROR:") 447 | assert pytest_wrapped_e.type == SystemExit 448 | assert pytest_wrapped_e.value.code == 1 449 | 450 | 451 | def test_ufodiff_commandline_diff_missing_branch(capsys): 452 | with pytest.raises(SystemExit) as pytest_wrapped_e: 453 | from ufodiff.app import main 454 | 455 | sys.argv = ["ufodiff", "diff", "branch:"] 456 | main() 457 | 458 | out, err = capsys.readouterr() 459 | assert err.startswith("[ufodiff] ERROR:") 460 | assert pytest_wrapped_e.type == SystemExit 461 | assert pytest_wrapped_e.value.code == 1 462 | 463 | 464 | def test_ufodiff_commandline_diffnc_missing_branch(capsys): 465 | with pytest.raises(SystemExit) as pytest_wrapped_e: 466 | from ufodiff.app import main 467 | 468 | sys.argv = ["ufodiff", "diffnc", "branch:"] 469 | main() 470 | 471 | out, err = capsys.readouterr() 472 | assert err.startswith("[ufodiff] ERROR:") 473 | assert pytest_wrapped_e.type == SystemExit 474 | assert pytest_wrapped_e.value.code == 1 475 | 476 | 477 | def test_ufodiff_commandline_diff_success_commits_arg(capsys): 478 | with pytest.raises(SystemExit) as pytest_wrapped_e: 479 | from ufodiff.app import main 480 | 481 | sys.argv = ["ufodiff", "diff", "commits:1"] 482 | main() 483 | 484 | out, err = capsys.readouterr() 485 | assert pytest_wrapped_e.type == SystemExit 486 | assert pytest_wrapped_e.value.code == 0 487 | 488 | 489 | def test_ufodiff_commandline_diffnc_success_commits_arg(capsys): 490 | with pytest.raises(SystemExit) as pytest_wrapped_e: 491 | from ufodiff.app import main 492 | 493 | sys.argv = ["ufodiff", "diffnc", "commits:1"] 494 | main() 495 | 496 | out, err = capsys.readouterr() 497 | assert pytest_wrapped_e.type == SystemExit 498 | assert pytest_wrapped_e.value.code == 0 499 | 500 | 501 | def test_ufodiff_commandline_diff_success_branch_arg(capsys): 502 | make_testing_branch() 503 | 504 | with pytest.raises(SystemExit) as pytest_wrapped_e: 505 | from ufodiff.app import main 506 | 507 | sys.argv = ["ufodiff", "diff", "branch:testing_branch"] 508 | main() 509 | 510 | out, err = capsys.readouterr() 511 | assert pytest_wrapped_e.type == SystemExit 512 | assert pytest_wrapped_e.value.code == 0 513 | 514 | delete_testing_branch() 515 | 516 | 517 | def test_ufodiff_commandline_diffnc_success_branch_arg(capsys): 518 | make_testing_branch() 519 | 520 | with pytest.raises(SystemExit) as pytest_wrapped_e: 521 | from ufodiff.app import main 522 | 523 | sys.argv = ["ufodiff", "diffnc", "branch:testing_branch"] 524 | main() 525 | 526 | out, err = capsys.readouterr() 527 | assert pytest_wrapped_e.type == SystemExit 528 | assert pytest_wrapped_e.value.code == 0 529 | 530 | delete_testing_branch() 531 | 532 | 533 | def test_ufodiff_commandline_diff_success_git_arg(capsys): 534 | with pytest.raises(SystemExit) as pytest_wrapped_e: 535 | from ufodiff.app import main 536 | 537 | sys.argv = ["ufodiff", "diff", "HEAD~1"] 538 | main() 539 | 540 | out, err = capsys.readouterr() 541 | assert pytest_wrapped_e.type == SystemExit 542 | assert pytest_wrapped_e.value.code == 0 543 | 544 | 545 | def test_ufodiff_commandline_diffnc_success_git_arg(capsys): 546 | with pytest.raises(SystemExit) as pytest_wrapped_e: 547 | from ufodiff.app import main 548 | 549 | sys.argv = ["ufodiff", "diffnc", "HEAD~1"] 550 | main() 551 | 552 | out, err = capsys.readouterr() 553 | assert pytest_wrapped_e.type == SystemExit 554 | assert pytest_wrapped_e.value.code == 0 555 | 556 | 557 | # raise exceptions in underlying GitPython (git import) library and demonstrate catch them 558 | 559 | 560 | def test_ufodiff_commandline_diff_exception_git_request(capsys): 561 | with pytest.raises(SystemExit) as pytest_wrapped_e: 562 | from ufodiff.app import main 563 | 564 | sys.argv = ["ufodiff", "diff", "bogus"] 565 | main() 566 | 567 | out, err = capsys.readouterr() 568 | assert err.startswith("[ufodiff] ERROR: ") 569 | assert pytest_wrapped_e.type == SystemExit 570 | assert pytest_wrapped_e.value.code == 1 571 | 572 | 573 | def test_ufodiff_commandline_diffnc_exception_git_request(capsys): 574 | with pytest.raises(SystemExit) as pytest_wrapped_e: 575 | from ufodiff.app import main 576 | 577 | sys.argv = ["ufodiff", "diffnc", "bogus"] 578 | main() 579 | 580 | out, err = capsys.readouterr() 581 | assert err.startswith("[ufodiff] ERROR: ") 582 | assert pytest_wrapped_e.type == SystemExit 583 | assert pytest_wrapped_e.value.code == 1 584 | 585 | 586 | # raise exception in git root path handling 587 | def test_ufodiff_commandline_git_repo_root_exception(capsys): 588 | with pytest.raises(SystemExit) as pytest_wrapped_e: 589 | with mock.patch("os.path.join") as opj: 590 | opj.side_effect = ( 591 | OSError() 592 | ) # mock an exception from os.path.join inside the function 593 | get_git_root_path() 594 | 595 | out, err = capsys.readouterr() 596 | assert err.startswith("[ufodiff] ERROR:") 597 | assert pytest_wrapped_e.type == SystemExit 598 | assert pytest_wrapped_e.value.code == 1 599 | -------------------------------------------------------------------------------- /tests/test_ufo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import pytest 7 | 8 | from ufodiff.utilities.ufo import Ufo 9 | 10 | # /////////////////////////////////////////////////////// 11 | # 12 | # Ufo class tests 13 | # 14 | # /////////////////////////////////////////////////////// 15 | 16 | ufo_acceptable_files = { 17 | "metainfo.plist", 18 | "fontinfo.plist", 19 | "groups.plist", 20 | "kerning.plist", 21 | "features.fea", 22 | "lib.plist", 23 | "contents.plist", 24 | "layercontents.plist", 25 | "layerinfo.plist", 26 | } 27 | 28 | unacceptable_file = "testing.py" 29 | 30 | 31 | # confirm valid file list in class property 32 | def test_ufodiff_ufo_acceptable_files(): 33 | ufo = Ufo() 34 | for the_file in ufo.acceptable_files: 35 | assert the_file in ufo_acceptable_files 36 | 37 | 38 | # confirm unacceptable file returns false from class property 39 | def test_ufodiff_ufo_unacceptable_file(): 40 | ufo = Ufo() 41 | assert (unacceptable_file in ufo.acceptable_files) is False 42 | 43 | 44 | # test ufo.validate_file() with true conditions 45 | def test_ufodiff_ufo_validate_file_true(): 46 | ufo = Ufo() 47 | for the_file in ufo_acceptable_files: 48 | assert ufo.validate_file(the_file) is True 49 | 50 | 51 | # test ufo.validate_file() with false condition 52 | def test_ufodiff_ufo_validate_file_false(): 53 | ufo = Ufo() 54 | assert ufo.validate_file("testing.py") is False 55 | 56 | 57 | # test nonglyph file check with true conditions 58 | def test_ufodiff_ufo_nonglyph_true(): 59 | ufo = Ufo() 60 | for ( 61 | nonglyph_file 62 | ) in ( 63 | ufo_acceptable_files 64 | ): # this file set only includes nonglyph files (i.e. not *.glif) 65 | assert ufo.is_nonglyph_file(nonglyph_file) is True 66 | 67 | 68 | # test nonglyph file check with false condition 69 | def test_ufodiff_ufo_nonglyph_false(): 70 | ufo = Ufo() 71 | assert ufo.is_nonglyph_file("A.glif") is False 72 | 73 | 74 | # test glyph file check with true condition 75 | def test_ufodiff_ufo_glyph_true(): 76 | ufo = Ufo() 77 | assert ufo.is_glyph_file("A.glif") is True 78 | assert ufo.is_glyph_file("A__.glif") is True 79 | assert ufo.is_glyph_file("0012.glif") is True 80 | 81 | 82 | # test glyph file check with false conditions 83 | def test_ufodiff_ufo_glyph_false(): 84 | ufo = Ufo() 85 | for nonglyph_file in ufo_acceptable_files: 86 | assert ufo.is_glyph_file(nonglyph_file) is False 87 | 88 | 89 | # test detection of metainfo.plist file with true condition 90 | # (location of UFO version definition in the source files) 91 | def test_ufodiff_ufo_version_true(): 92 | ufo = Ufo() 93 | assert ufo.is_ufo_version_file("metainfo.plist") is True 94 | 95 | 96 | # test detection of metainfo.plist file with false condition 97 | def test_ufodiff_ufo_version_false(): 98 | ufo = Ufo() 99 | assert ufo.is_ufo_version_file("fontinfo.plist") is False 100 | 101 | 102 | # test UFO file filters for diff and diffnc commands 103 | def test_ufodiff_ufo_diff_filters(): 104 | ufo = Ufo() 105 | filter_test_list = ufo.get_valid_file_filterlist_for_diff() 106 | # test for each of the nonglyph files 107 | for acceptable_file in ufo.acceptable_files: 108 | filter_file = "*" + acceptable_file 109 | assert filter_file in filter_test_list 110 | # test for inclusion of glyph files 111 | assert "*.glif" in filter_test_list 112 | assert r"*\.ufo/images/*" in filter_test_list 113 | assert r"*\.ufo/data/*" in filter_test_list 114 | 115 | 116 | # UFO v3 image directory/file tests 117 | 118 | 119 | def test_ufodiff_ufo_images_dir_png_file_true(): 120 | ufo = Ufo() 121 | test_path_1 = os.path.join("source", "Test-Regular.ufo", "images", "cap_a.png") 122 | assert ufo._is_images_directory_file(test_path_1) is True 123 | 124 | 125 | def test_ufodiff_ufo_images_dir_png_file_false(): 126 | ufo = Ufo() 127 | test_path_1 = os.path.join( 128 | "source", "anotherdir", "images", "cap_a.png" 129 | ) # not a UFO source directory 130 | test_path_2 = os.path.join( 131 | "source", "Test-Regular.ufo", "image", "cap_a.png" 132 | ) # incorrect images dir path 133 | test_path_3 = os.path.join( 134 | "source", "Test-Regular.ufo", "images", "cap_a.jpg" 135 | ) # jpg file, not png 136 | assert ufo._is_images_directory_file(test_path_1) is False 137 | assert ufo._is_images_directory_file(test_path_2) is False 138 | assert ufo._is_images_directory_file(test_path_3) is False 139 | 140 | 141 | def test_ufodiff_ufo_images_dir_png_file_via_public_method_call(): 142 | ufo = Ufo() 143 | test_path_1 = os.path.join( 144 | "source", "anotherdir", "images", "cap_a.png" 145 | ) # not a UFO source directory (False) 146 | test_path_2 = os.path.join( 147 | "source", "Test-Regular.ufo", "image", "cap_a.png" 148 | ) # incorrect images dir path (False) 149 | test_path_3 = os.path.join( 150 | "source", "Test-Regular.ufo", "images", "cap_a.jpg" 151 | ) # jpg file, not png (False) 152 | test_path_4 = os.path.join( 153 | "source", "Test-Regular.ufo", "images", "cap_a.png" 154 | ) # good path (True) 155 | assert ufo.validate_file(test_path_1) is False 156 | assert ufo.validate_file(test_path_2) is False 157 | assert ufo.validate_file(test_path_3) is False 158 | assert ufo.validate_file(test_path_4) is True 159 | 160 | 161 | # UFO v3 data directory/file tests 162 | def test_ufodiff_ufo_data_dir_file_true(): 163 | ufo = Ufo() 164 | test_path_1 = os.path.join( 165 | "source", "Test-Regular.ufo", "data", "org.sourcefoundry.coolstuff" 166 | ) 167 | assert ufo._is_data_directory_file(test_path_1) is True 168 | 169 | 170 | def test_ufodiff_ufo_data_dir_file_false(): 171 | ufo = Ufo() 172 | test_path_1 = os.path.join( 173 | "source", "Test-Regular.ufo", "datum", "org.sourcefoundry.coolstuff" 174 | ) # bad dir path 175 | test_path_2 = os.path.join( 176 | "source", "Test-Regular", "data", "org.sourcefoundry.coolstuff" 177 | ) # not ufo dir 178 | assert ufo._is_data_directory_file(test_path_1) is False 179 | assert ufo._is_data_directory_file(test_path_2) is False 180 | 181 | 182 | def test_ufodiff_ufo_data_dir_file_via_public_method_call(): 183 | ufo = Ufo() 184 | test_path_1 = os.path.join( 185 | "source", "Test-Regular.ufo", "datum", "org.sourcefoundry.coolstuff" 186 | ) # bad dir path (False) 187 | test_path_2 = os.path.join( 188 | "source", "Test-Regular", "data", "org.sourcefoundry.coolstuff" 189 | ) # not ufo dir (False) 190 | test_path_3 = os.path.join( 191 | "source", "Test-Regular.ufo", "data", "org.sourcefoundry.coolstuff" 192 | ) # good path (True) 193 | assert ufo.validate_file(test_path_1) is False 194 | assert ufo.validate_file(test_path_2) is False 195 | assert ufo.validate_file(test_path_3) is True 196 | -------------------------------------------------------------------------------- /tests/test_utilities.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | import os.path 6 | import pytest 7 | 8 | from ufodiff.utilities import dir_exists, file_exists 9 | 10 | # /////////////////////////////////////////////////////// 11 | # 12 | # Utility function tests (utilities.__init__.py) 13 | # 14 | # /////////////////////////////////////////////////////// 15 | 16 | valid_directory_test_path = os.path.join("tests", "testfiles") 17 | valid_file_test_path = os.path.join("tests", "testfiles", "testfile.txt") 18 | 19 | invalid_directory_test_path = os.path.join("tests", "bogusdir") 20 | invalid_file_test_path = os.path.join("tests", "testfiles", "totallybogus_file.txt") 21 | 22 | 23 | def test_ufodiff_utilities_dir_exists_true(): 24 | assert dir_exists(valid_directory_test_path) is True 25 | 26 | 27 | def test_ufodiff_utilities_file_exists_true(): 28 | assert file_exists(valid_file_test_path) is True 29 | 30 | 31 | def test_ufodiff_utilities_dir_exists_false(): 32 | assert dir_exists(invalid_directory_test_path) is False 33 | 34 | 35 | def test_ufodiff_utilities_file_exists_false(): 36 | assert file_exists(invalid_file_test_path) is False 37 | -------------------------------------------------------------------------------- /tests/testfiles/depth2/depth3/depth4/testdepth4.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/source-foundry/ufodiff/548a1c427322a05ba9b592d62f6f10eb565029d9/tests/testfiles/depth2/depth3/depth4/testdepth4.txt -------------------------------------------------------------------------------- /tests/testfiles/depth2/depth3/testdepth3.txt: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /tests/testfiles/depth2/testdepth2.txt: -------------------------------------------------------------------------------- 1 | testing -------------------------------------------------------------------------------- /tests/testfiles/testfile.txt: -------------------------------------------------------------------------------- 1 | This is a simple test file -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36, py37, py38 3 | 4 | [testenv] 5 | commands = 6 | py.test tests 7 | 8 | deps = 9 | -rrequirements.txt 10 | pytest 11 | pytest-mock 12 | mock 13 | coverage 14 | codecov 15 | --------------------------------------------------------------------------------