├── .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 | [](https://pypi.org/project/ufodiff)
4 | [](https://travis-ci.com/source-foundry/ufodiff)
5 | [](https://ci.appveyor.com/project/chrissimpkins/ufodiff/branch/master)
6 | 
7 | [](https://codecov.io/gh/source-foundry/ufodiff)
8 | [](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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------