From cef7507403afb474313111e6960aad8527406c18 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Sat, 3 Dec 2022 02:09:49 +0000 Subject: [PATCH 01/38] Add matchcode to purldb repo Signed-off-by: Jono Yang --- matchcode/.gitattributes | 3 + matchcode/.gitignore | 75 ++ matchcode/AUTHORS.rst | 3 + matchcode/CHANGELOG.rst | 53 ++ matchcode/LICENSE | 2 + matchcode/MANIFEST.in | 15 + matchcode/Makefile | 124 +++ matchcode/README.rst | 66 ++ matchcode/configure | 204 +++++ matchcode/manage.py | 14 + matchcode/pyproject.toml | 52 ++ matchcode/requirements-dev.txt | 14 + matchcode/requirements.txt | 89 ++ matchcode/setup.cfg | 67 ++ matchcode/setup.py | 6 + matchcode/src/matchcode/__init__.py | 0 matchcode/src/matchcode/api.py | 259 ++++++ matchcode/src/matchcode/api_custom.py | 17 + matchcode/src/matchcode/fingerprinting.py | 105 +++ matchcode/src/matchcode/halohash.py | 406 ++++++++ matchcode/src/matchcode/hash.py | 131 +++ matchcode/src/matchcode/indexing.py | 155 ++++ .../src/matchcode/management/__init__.py | 0 .../matchcode/management/commands/__init__.py | 32 + .../management/commands/index_packages.py | 78 ++ .../management/commands/match_scan.py | 31 + matchcode/src/matchcode/match.py | 222 +++++ .../src/matchcode/migrations/0001_initial.py | 104 +++ .../src/matchcode/migrations/__init__.py | 0 matchcode/src/matchcode/models.py | 376 ++++++++ matchcode/src/matchcode/utils.py | 182 ++++ matchcode/src/matchcodeio/__init__.py | 0 .../src/matchcodeio/settings/__init__.py | 277 ++++++ .../src/matchcodeio/settings/dbrouter.py | 21 + matchcode/src/matchcodeio/urls.py | 28 + matchcode/src/matchcodeio/wsgi.py | 18 + matchcode/tests/__init__.py | 0 matchcode/tests/matchcode/__init__.py | 0 matchcode/tests/matchcode/test_api.py | 159 ++++ .../tests/matchcode/test_fingerprinting.py | 101 ++ .../tests/matchcode/test_index_packages.py | 221 +++++ matchcode/tests/matchcode/test_match.py | 333 +++++++ matchcode/tests/matchcode/test_models.py | 223 +++++ .../testfiles/api/scan2_match_results.json | 235 +++++ .../fingerprinting/abbrev-1.0.3-i.json | 161 ++++ .../abbrev-1.0.3-i-expected.json | 144 +++ .../directory-matching/abbrev-1.0.3-i.json | 161 ++++ .../abbrev-1.0.4-i-expected.json | 172 ++++ .../directory-matching/abbrev-1.0.4-i.json | 186 ++++ .../abbrev-1.0.5-i-expected.json | 200 ++++ .../directory-matching/abbrev-1.0.5-i.json | 211 +++++ .../abbrev-1.0.6-i-expected.json | 200 ++++ .../directory-matching/abbrev-1.0.6-i.json | 211 +++++ .../abbrev-1.0.7-i-expected.json | 256 ++++++ .../directory-matching/abbrev-1.0.7-i.json | 261 ++++++ .../abbrev-1.0.9-i-expected.json | 144 +++ .../directory-matching/abbrev-1.0.9-i.json | 161 ++++ .../abbrev-1.1.0-i-expected.json | 144 +++ .../directory-matching/abbrev-1.1.0-i.json | 161 ++++ .../abbrev-1.1.1-i-expected.json | 144 +++ .../directory-matching/abbrev-1.1.1-i.json | 161 ++++ .../get-stdin-3.0.2-i-expected.json | 108 +++ .../directory-matching/get-stdin-3.0.2-i.json | 136 +++ .../match/nested/nested-expected.json | 650 +++++++++++++ .../testfiles/match/nested/nested.json | 637 +++++++++++++ .../match/nested/new-nested-expected.json | 691 ++++++++++++++ .../testfiles/match/nested/new-nested.json | 866 ++++++++++++++++++ .../match/nested/plugin-request-2.4.1-ip.json | 353 +++++++ .../match/nested/underscore-1.10.9-ip.json | 260 ++++++ .../testfiles/match/nested/underscore.json | 392 ++++++++ .../matchcode/testfiles/match/scan1.json | 135 +++ .../async-0.2.10.tgz-i.json | 201 ++++ .../async-0.2.9-i-expected-content.json | 224 +++++ .../async-0.2.9-i-expected-structure.json | 224 +++++ .../directory-matching/async-0.2.9-i.json | 201 ++++ ...file-matching-standalone-test-results.json | 187 ++++ ...approximate-directory-content-results.json | 193 ++++ ...proximate-directory-structure-results.json | 193 ++++ .../models/match-test-exact-file-results.json | 187 ++++ .../match-test-exact-package-results.json | 185 ++++ .../testfiles/models/match-test.json | 194 ++++ matchcode/tests/test_skeleton_codestyle.py | 36 + packagedb/src/packagedbio/wsgi.py | 2 +- 83 files changed, 13833 insertions(+), 1 deletion(-) create mode 100644 matchcode/.gitattributes create mode 100644 matchcode/.gitignore create mode 100644 matchcode/AUTHORS.rst create mode 100644 matchcode/CHANGELOG.rst create mode 100644 matchcode/LICENSE create mode 100644 matchcode/MANIFEST.in create mode 100644 matchcode/Makefile create mode 100644 matchcode/README.rst create mode 100755 matchcode/configure create mode 100644 matchcode/manage.py create mode 100644 matchcode/pyproject.toml create mode 100644 matchcode/requirements-dev.txt create mode 100644 matchcode/requirements.txt create mode 100644 matchcode/setup.cfg create mode 100644 matchcode/setup.py create mode 100644 matchcode/src/matchcode/__init__.py create mode 100644 matchcode/src/matchcode/api.py create mode 100644 matchcode/src/matchcode/api_custom.py create mode 100644 matchcode/src/matchcode/fingerprinting.py create mode 100644 matchcode/src/matchcode/halohash.py create mode 100644 matchcode/src/matchcode/hash.py create mode 100644 matchcode/src/matchcode/indexing.py create mode 100644 matchcode/src/matchcode/management/__init__.py create mode 100644 matchcode/src/matchcode/management/commands/__init__.py create mode 100644 matchcode/src/matchcode/management/commands/index_packages.py create mode 100644 matchcode/src/matchcode/management/commands/match_scan.py create mode 100644 matchcode/src/matchcode/match.py create mode 100644 matchcode/src/matchcode/migrations/0001_initial.py create mode 100644 matchcode/src/matchcode/migrations/__init__.py create mode 100644 matchcode/src/matchcode/models.py create mode 100644 matchcode/src/matchcode/utils.py create mode 100644 matchcode/src/matchcodeio/__init__.py create mode 100644 matchcode/src/matchcodeio/settings/__init__.py create mode 100644 matchcode/src/matchcodeio/settings/dbrouter.py create mode 100644 matchcode/src/matchcodeio/urls.py create mode 100644 matchcode/src/matchcodeio/wsgi.py create mode 100644 matchcode/tests/__init__.py create mode 100644 matchcode/tests/matchcode/__init__.py create mode 100644 matchcode/tests/matchcode/test_api.py create mode 100644 matchcode/tests/matchcode/test_fingerprinting.py create mode 100644 matchcode/tests/matchcode/test_index_packages.py create mode 100644 matchcode/tests/matchcode/test_match.py create mode 100644 matchcode/tests/matchcode/test_models.py create mode 100644 matchcode/tests/matchcode/testfiles/api/scan2_match_results.json create mode 100644 matchcode/tests/matchcode/testfiles/fingerprinting/abbrev-1.0.3-i.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.3-i-expected.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.3-i.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.4-i-expected.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.4-i.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.5-i-expected.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.5-i.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.6-i-expected.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.6-i.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.7-i-expected.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.7-i.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.9-i-expected.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.9-i.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.0-i-expected.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.0-i.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.1-i-expected.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.1-i.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/get-stdin-3.0.2-i-expected.json create mode 100644 matchcode/tests/matchcode/testfiles/match/directory-matching/get-stdin-3.0.2-i.json create mode 100644 matchcode/tests/matchcode/testfiles/match/nested/nested-expected.json create mode 100644 matchcode/tests/matchcode/testfiles/match/nested/nested.json create mode 100644 matchcode/tests/matchcode/testfiles/match/nested/new-nested-expected.json create mode 100644 matchcode/tests/matchcode/testfiles/match/nested/new-nested.json create mode 100644 matchcode/tests/matchcode/testfiles/match/nested/plugin-request-2.4.1-ip.json create mode 100644 matchcode/tests/matchcode/testfiles/match/nested/underscore-1.10.9-ip.json create mode 100644 matchcode/tests/matchcode/testfiles/match/nested/underscore.json create mode 100644 matchcode/tests/matchcode/testfiles/match/scan1.json create mode 100644 matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.10.tgz-i.json create mode 100644 matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i-expected-content.json create mode 100644 matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i-expected-structure.json create mode 100644 matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i.json create mode 100644 matchcode/tests/matchcode/testfiles/models/exact-file-matching-standalone-test-results.json create mode 100644 matchcode/tests/matchcode/testfiles/models/match-test-approximate-directory-content-results.json create mode 100644 matchcode/tests/matchcode/testfiles/models/match-test-approximate-directory-structure-results.json create mode 100644 matchcode/tests/matchcode/testfiles/models/match-test-exact-file-results.json create mode 100644 matchcode/tests/matchcode/testfiles/models/match-test-exact-package-results.json create mode 100644 matchcode/tests/matchcode/testfiles/models/match-test.json create mode 100644 matchcode/tests/test_skeleton_codestyle.py diff --git a/matchcode/.gitattributes b/matchcode/.gitattributes new file mode 100644 index 00000000..96c89ceb --- /dev/null +++ b/matchcode/.gitattributes @@ -0,0 +1,3 @@ +# Ignore all Git auto CR/LF line endings conversions +* -text +pyproject.toml export-subst diff --git a/matchcode/.gitignore b/matchcode/.gitignore new file mode 100644 index 00000000..c8555a1a --- /dev/null +++ b/matchcode/.gitignore @@ -0,0 +1,75 @@ +# Python compiled files +*.py[cod] + +# virtualenv and other misc bits +*.egg-info +/dist +/build +/bin +/lib +/scripts +/Scripts +/Lib +/pip-selfcheck.json +/tmp +/venv +.Python +/include +/Include +/local +*/local/* +/local/ +/share/ +/tcl/ +/.eggs/ +pip-wheel-metadata/ +/venv + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.cache +.coverage +.coverage.* +nosetests.xml +htmlcov + +# Translations +*.mo + +# IDEs +.project +.pydevproject +.idea +org.eclipse.core.resources.prefs +.vscode +.vs + +# Sphinx +docs/_build +docs/bin +docs/build +docs/include +docs/Lib +doc/pyvenv.cfg +pyvenv.cfg + +# Various junk and temp files +.DS_Store +*~ +.*.sw[po] +.build +.ve +*.bak +/.cache/ + +# pyenv +/.python-version +/man/ +/.pytest_cache/ +lib64 +tcl + +# Ignore Jupyter Notebook related temp files +.ipynb_checkpoints/ diff --git a/matchcode/AUTHORS.rst b/matchcode/AUTHORS.rst new file mode 100644 index 00000000..51a19cc8 --- /dev/null +++ b/matchcode/AUTHORS.rst @@ -0,0 +1,3 @@ +The following organizations or individuals have contributed to this repo: + +- diff --git a/matchcode/CHANGELOG.rst b/matchcode/CHANGELOG.rst new file mode 100644 index 00000000..678111de --- /dev/null +++ b/matchcode/CHANGELOG.rst @@ -0,0 +1,53 @@ +Changelog +========= + +next-next-next-release (2022-XX-XX) +----------------------------------- + * Bump dependency versions + * Update Django to 4.0.4 + * Update djangorestframework to 3.13.1 + * Update django-filter to 21.1 + * Update packagedb to 1.2.3 + * Update psycopg2 to 2.9.3 + * Use django.utils.translation.gettext_lazy instead of django.utils.translation.ugettext_lazy + * Use django.urls.re_path instead of django.conf.urls.url + +next-next-release (2021-XX-XX) +------------------------------ + + * Remove MatchRequest and Match models and APIs from matchcode. Matchcode will provide API + access to the different matching indices and return results for individual + fingerprint lookups instead of running the matching process on the entire + codebase of Resources at once. + * Reorganize fingerprinting, indexing, and matching functions into their own files. + * Modify matching functions and tests to work without the MatchRequest and Match models. + These functions will go into a scanpipe pipes/pipeline later. + +next-release (2021-XX-XX) +------------------------- + + * Improved approximate directory structure and approximate directory content + matching available through API + * We now return exact directory matches, if available. If no exact match is found, + the closest matches are returned. + * Package matches are placed in top-level ``packages`` field in JSON results. + Package matches are related to files using the purl of a package. + * Show stats when the command index_packages is done running + * Send match completion notifications via webhooks. MatchRequest API now + accepts webhook URLs during creation. + * Delay running matching task for 10 seconds after a MatchRequest is received. + This is to ensure that the received MatchRequest is written to the database before being used. + * The field `indexed_elements_count` has been added to `BaseDirectoryIndex`. + `indexed_elements_count` is an integer that represents the number of inputs used to create the + fingerprint. During a match, a fingerprint is compared to other fingerprints whose size is + within 5% of our fingerprints size. + * File size is now used in the creation of ApproximateDirectoryStructureIndex fingerprints. + +v0.0.1 (201X-XX-XX) +------------------- + + * Initial release + * SHA1 Package matching available through API (``api/match_request``) + * label, uuid, created_date, task_start_date, task_end_date, status, + execution_time, input_scan, and match_results for a given match request + available through API diff --git a/matchcode/LICENSE b/matchcode/LICENSE new file mode 100644 index 00000000..441ebbe0 --- /dev/null +++ b/matchcode/LICENSE @@ -0,0 +1,2 @@ +Confidential and Proprietary nexB Inc. +Copyright (c) nexB Inc. All rights reserved. \ No newline at end of file diff --git a/matchcode/MANIFEST.in b/matchcode/MANIFEST.in new file mode 100644 index 00000000..ef3721e8 --- /dev/null +++ b/matchcode/MANIFEST.in @@ -0,0 +1,15 @@ +graft src + +include *.LICENSE +include NOTICE +include *.ABOUT +include *.toml +include *.yml +include *.rst +include setup.* +include configure* +include requirements* +include .git* + +global-exclude *.py[co] __pycache__ *.*~ + diff --git a/matchcode/Makefile b/matchcode/Makefile new file mode 100644 index 00000000..cdb5fbe2 --- /dev/null +++ b/matchcode/Makefile @@ -0,0 +1,124 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +# Python version can be specified with `$ PYTHON_EXE=python3.x make conf` +PYTHON_EXE?=python3 +VENV=venv +MANAGE=${VENV}/bin/python manage.py +ACTIVATE?=. ${VENV}/bin/activate; +VIRTUALENV_PYZ=../etc/thirdparty/virtualenv.pyz +# Do not depend on Python to generate the SECRET_KEY +GET_SECRET_KEY=`base64 /dev/urandom | head -c50` +# Customize with `$ make envfile ENV_FILE=/etc/purldb/.env` +ENV_FILE=.env +# Customize with `$ make postgres MATCHCODEIO_DB_PASSWORD=YOUR_PASSWORD` +MATCHCODEIO_DB_PASSWORD=matc34-u5er + +# Use sudo for postgres, but only on Linux +UNAME := $(shell uname) +ifeq ($(UNAME), Linux) + SUDO_POSTGRES=sudo -u postgres +else + SUDO_POSTGRES= +endif + +virtualenv: + @echo "-> Bootstrap the virtualenv with PYTHON_EXE=${PYTHON_EXE}" + @${PYTHON_EXE} ${VIRTUALENV_PYZ} --never-download --no-periodic-update ${VENV} + +conf: + @echo "-> Install dependencies" + @./configure + +dev: + @echo "-> Configure and install development dependencies" + @./configure --dev + +envfile: + @echo "-> Create the .env file and generate a secret key" + @if test -f ${ENV_FILE}; then echo ".env file exists already"; exit 1; fi + @mkdir -p $(shell dirname ${ENV_FILE}) && touch ${ENV_FILE} + @echo SECRET_KEY=\"${GET_SECRET_KEY}\" > ${ENV_FILE} + +envfile-ci: envfile + @echo "-> Add Azure Pipelines Postgres DB name and password to .env" + @echo MATCHCODEIO_DB_USER="postgres" >> ${ENV_FILE} + @echo MATCHCODEIO_DB_PASSWORD="postgres" >> ${ENV_FILE} + @echo PACKAGEDB_DB_USER="postgres" >> ${ENV_FILE} + @echo PACKAGEDB_DB_PASSWORD="postgres" >> ${ENV_FILE} + +isort: + @echo "-> Apply isort changes to ensure proper imports ordering" + ${VENV}/bin/isort . + +black: + @echo "-> Apply black code formatter" + ${VENV}/bin/black . + +doc8: + @echo "-> Run doc8 validation" + @${ACTIVATE} doc8 --max-line-length 100 --ignore-path docs/_build/ --quiet docs/ + +valid: isort black + +check: + @echo "-> Run pycodestyle (PEP8) validation" + @${ACTIVATE} pycodestyle --max-line-length=100 --exclude=venv,lib,thirdparty,docs,migrations,settings.py . + @echo "-> Run isort imports ordering validation" + @${ACTIVATE} isort --check-only . + @echo "-> Run black validation" + @${ACTIVATE} black --check ${BLACK_ARGS} + +clean: + @echo "-> Clean the Python env" + @./configure --clean + +migrate: + @echo "-> Apply database migrations" + ${MANAGE} migrate + +postgres: + @echo "-> Configure PostgreSQL database" + @echo "-> Create database user 'matchcode'" + ${SUDO_POSTGRES} createuser --no-createrole --no-superuser --login --inherit --createdb matchcode || true + ${SUDO_POSTGRES} psql -c "alter user matchcode with encrypted password '${MATCHCODEIO_DB_PASSWORD}';" || true + @echo "-> Drop 'matchcode' database" + ${SUDO_POSTGRES} dropdb matchcode || true + @echo "-> Create 'matchcode' database" + ${SUDO_POSTGRES} createdb --encoding=utf-8 --owner=matchcode matchcode + @$(MAKE) migrate + +run: + ${MANAGE} runserver 8001 --insecure + +test: + @echo "-> Run the test suite" + ${ACTIVATE} DJANGO_SETTINGS_MODULE=matchcodeio.settings ${PYTHON_EXE} -m pytest -vvs + +shell: + ${MANAGE} shell + +bump: + @echo "-> Bump the version" + bin/bumpver update --no-fetch --patch + +docs: + rm -rf docs/_build/ + @${ACTIVATE} sphinx-build docs/ docs/_build/ + +docker-images: + @echo "-> Build Docker services" + docker-compose build + @echo "-> Pull service images" + docker-compose pull + @echo "-> Save the service images to a compressed tar archive in the dist/ directory" + @mkdir -p dist/ + @docker save minecode minecode_minecode nginx | gzip > dist/minecode-images-`git describe --tags`.tar.gz + +.PHONY: virtualenv conf dev envfile install check valid isort clean migrate postgres sqlite run test bump docs docker-images diff --git a/matchcode/README.rst b/matchcode/README.rst new file mode 100644 index 00000000..0163bf55 --- /dev/null +++ b/matchcode/README.rst @@ -0,0 +1,66 @@ +========= +MatchCode +========= +MatchCode is a server that detects Packages from ScanCode scans of a codebase. + +Installation +------------ +Requirements +############ +* Debian-based Linux distribution +* Python 3.6 +* Postgres 10 +* git +* PackageDB (https://github.com/nexB/packagedb) +* ClearCode Toolkit (https://github.com/nexB/clearcode-toolkit) + +Once the prerequisites have been installed, set up MatchCode with the following commands: +:: + + git clone https://github.com/nexB/matchcode.git + cd matchcode + source configure + sudo -u postgres createuser --no-createrole --no-superuser --login --inherit --createdb --pwprompt matchcode + sudo -u postgres createdb --encoding=utf-8 matchcode --owner=matchcode + # update your local DB + python manage.py migrate + +Once MatchCode and the database has been set up, run tests to ensure functionality: +:: + + python manage.py test + +Post-Installation +----------------- +If you have an empty PackageDB without Package and Package Resource information, ClearCode Toolkit should be run for a while so it can populate the PackageDB with Package and Package Resource information from clearlydefined. +The Package and Package Resource information will be used to create the matching indices. +Once the PackageDB has been populated, run the following command to create the matching indices from the collected Package data: +:: + python manage.py index_packages + + +Usage +----- +Start the MatchCode server by running: +:: + python manage.py runserver + +You can send a ScanCode JSON scan for matching at the api/match_request/ endpoint using the HTML view or API. + +There are currently four types of matching that MatchCode provides: + +* Exact Package archive matching + + * Check the SHA1 values of archives from a scan to determine if they are known Packages + +* Exact Package file matching + + * Check the SHA1 values of files from a scan to see what Packages also has that file + +* Approximate Directory structure matching + + * Check to see if a directory and the files under it is from a known Package using the name of the files + +* Approximate Directory content matching + + * Check to see if a directory and the files under it is from a known Package using the SHA1 values of the files diff --git a/matchcode/configure b/matchcode/configure new file mode 100755 index 00000000..e777a5d2 --- /dev/null +++ b/matchcode/configure @@ -0,0 +1,204 @@ +#!/usr/bin/env bash +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/ for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +set -e +#set -x + +################################ +# A configuration script to set things up: +# create a virtualenv and install or update thirdparty packages. +# Source this script for initial configuration +# Use configure --help for details +# +# NOTE: please keep in sync with Windows script configure.bat +# +# This script will search for a virtualenv.pyz app in etc/thirdparty/virtualenv.pyz +# Otherwise it will download the latest from the VIRTUALENV_PYZ_URL default +################################ +CLI_ARGS=$1 + +################################ +# Defaults. Change these variables to customize this script +################################ + +CUSTOM_PACKAGES="https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip" + +# Requirement arguments passed to pip and used by default or with --dev. +REQUIREMENTS="--editable . --constraint requirements.txt $CUSTOM_PACKAGES" +DEV_REQUIREMENTS="--editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt $CUSTOM_PACKAGES" +DOCS_REQUIREMENTS="--editable .[docs] --constraint requirements.txt $CUSTOM_PACKAGES" + +# where we create a virtualenv +VIRTUALENV_DIR=venv + +# Cleanable files and directories to delete with the --clean option +CLEANABLE="build venv" + +# extra arguments passed to pip +PIP_EXTRA_ARGS=" " + +# the URL to download virtualenv.pyz if needed +VIRTUALENV_PYZ_URL=https://bootstrap.pypa.io/virtualenv.pyz +################################ + + +################################ +# Current directory where this script lives +CFG_ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +CFG_BIN_DIR=$CFG_ROOT_DIR/$VIRTUALENV_DIR/bin + + +################################ +# Install with or without and index. With "--no-index" this is using only local wheels +# This is an offline mode with no index and no network operations +# NO_INDEX="--no-index " +NO_INDEX="" + + +################################ +# Thirdparty package locations and index handling +# Find packages from the local thirdparty directory if present +THIRDPARDIR=$CFG_ROOT_DIR/thirdparty +if [[ "$(echo $THIRDPARDIR/*.whl)x" != "$THIRDPARDIR/*.whlx" ]]; then + PIP_EXTRA_ARGS="$NO_INDEX --find-links $THIRDPARDIR" +fi + + +################################ +# Set the quiet flag to empty if not defined +if [[ "$CFG_QUIET" == "" ]]; then + CFG_QUIET=" " +fi + + +################################ +# Find a proper Python to run +# Use environment variables or a file if available. +# Otherwise the latest Python by default. +find_python() { + if [[ "$PYTHON_EXECUTABLE" == "" ]]; then + # check for a file named PYTHON_EXECUTABLE + if [ -f "$CFG_ROOT_DIR/PYTHON_EXECUTABLE" ]; then + PYTHON_EXECUTABLE=$(cat "$CFG_ROOT_DIR/PYTHON_EXECUTABLE") + else + PYTHON_EXECUTABLE=python3 + fi + fi +} + + +################################ +create_virtualenv() { + # create a virtualenv for Python + # Note: we do not use the bundled Python 3 "venv" because its behavior and + # presence is not consistent across Linux distro and sometimes pip is not + # included either by default. The virtualenv.pyz app cures all these issues. + + VENV_DIR="$1" + if [ ! -f "$CFG_BIN_DIR/python" ]; then + + mkdir -p "$CFG_ROOT_DIR/$VENV_DIR" + + if [ -f "$CFG_ROOT_DIR/etc/thirdparty/virtualenv.pyz" ]; then + VIRTUALENV_PYZ="$CFG_ROOT_DIR/etc/thirdparty/virtualenv.pyz" + else + VIRTUALENV_PYZ="$CFG_ROOT_DIR/$VENV_DIR/virtualenv.pyz" + wget -O "$VIRTUALENV_PYZ" "$VIRTUALENV_PYZ_URL" 2>/dev/null || curl -o "$VIRTUALENV_PYZ" "$VIRTUALENV_PYZ_URL" + fi + + $PYTHON_EXECUTABLE "$VIRTUALENV_PYZ" \ + --wheel embed --pip embed --setuptools embed \ + --seeder pip \ + --never-download \ + --no-periodic-update \ + --no-vcs-ignore \ + $CFG_QUIET \ + "$CFG_ROOT_DIR/$VENV_DIR" + fi +} + + +################################ +install_packages() { + # install requirements in virtualenv + # note: --no-build-isolation means that pip/wheel/setuptools will not + # be reinstalled a second time and reused from the virtualenv and this + # speeds up the installation. + # We always have the PEP517 build dependencies installed already. + + "$CFG_BIN_DIR/pip" install \ + --upgrade \ + --no-build-isolation \ + $CFG_QUIET \ + $PIP_EXTRA_ARGS \ + $1 +} + + +################################ +cli_help() { + echo An initial configuration script + echo " usage: ./configure [options]" + echo + echo The default is to configure for regular use. Use --dev for development. + echo + echo The options are: + echo " --clean: clean built and installed files and exit." + echo " --dev: configure the environment for development." + echo " --help: display this help message and exit." + echo + echo By default, the python interpreter version found in the path is used. + echo Alternatively, the PYTHON_EXECUTABLE environment variable can be set to + echo configure another Python executable interpreter to use. If this is not + echo set, a file named PYTHON_EXECUTABLE containing a single line with the + echo path of the Python executable to use will be checked last. + set +e + exit +} + + +################################ +clean() { + # Remove cleanable file and directories and files from the root dir. + echo "* Cleaning ..." + for cln in $CLEANABLE; + do rm -rf "${CFG_ROOT_DIR:?}/${cln:?}"; + done + set +e + exit +} + + +################################ +# Main command line entry point +CFG_REQUIREMENTS=$REQUIREMENTS + +# We are using getopts to parse option arguments that start with "-" +while getopts :-: optchar; do + case "${optchar}" in + -) + case "${OPTARG}" in + help ) cli_help;; + clean ) find_python && clean;; + dev ) CFG_REQUIREMENTS="$DEV_REQUIREMENTS";; + docs ) CFG_REQUIREMENTS="$DOCS_REQUIREMENTS";; + esac;; + esac +done + + +PIP_EXTRA_ARGS="$PIP_EXTRA_ARGS" + +find_python +create_virtualenv "$VIRTUALENV_DIR" +install_packages "$CFG_REQUIREMENTS" +. "$CFG_BIN_DIR/activate" + + +set +e diff --git a/matchcode/manage.py b/matchcode/manage.py new file mode 100644 index 00000000..847e3465 --- /dev/null +++ b/matchcode/manage.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# +# Copyright (c) nexB Inc. http://www.nexb.com/ - All rights reserved. +# + +import os +import sys + + +if __name__ == '__main__': + from django.core.management import execute_from_command_line + + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'matchcodeio.settings') + execute_from_command_line(sys.argv) diff --git a/matchcode/pyproject.toml b/matchcode/pyproject.toml new file mode 100644 index 00000000..cde79074 --- /dev/null +++ b/matchcode/pyproject.toml @@ -0,0 +1,52 @@ +[build-system] +requires = ["setuptools >= 50", "wheel", "setuptools_scm[toml] >= 6"] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +# this is used populated when creating a git archive +# and when there is .git dir and/or there is no git installed +fallback_version = "9999.$Format:%h-%cs$" + +[tool.pytest.ini_options] +norecursedirs = [ + ".git", + "bin", + "dist", + "build", + "_build", + "dist", + "etc", + "local", + "ci", + "docs", + "man", + "share", + "samples", + ".cache", + ".settings", + "Include", + "include", + "Lib", + "lib", + "lib64", + "Lib64", + "Scripts", + "thirdparty", + "tmp", + "venv", + "tests/data", + ".eggs", + "src/*/data", + "tests/*/data" +] + +python_files = "*.py" + +python_classes = "Test" +python_functions = "test" + +addopts = [ + "-rfExXw", + "--strict-markers", + "--doctest-modules" +] diff --git a/matchcode/requirements-dev.txt b/matchcode/requirements-dev.txt new file mode 100644 index 00000000..65b64a2b --- /dev/null +++ b/matchcode/requirements-dev.txt @@ -0,0 +1,14 @@ +aboutcode-toolkit==7.2.0 +black==22.10.0 +et-xmlfile==1.1.0 +exceptiongroup==1.0.4 +execnet==1.9.0 +iniconfig==1.1.1 +mypy-extensions==0.4.3 +openpyxl==3.0.10 +pathspec==0.10.2 +platformdirs==2.5.4 +pytest==7.2.0 +pytest-django==4.5.2 +pytest-xdist==3.0.2 +tomli==2.0.1 \ No newline at end of file diff --git a/matchcode/requirements.txt b/matchcode/requirements.txt new file mode 100644 index 00000000..ae091e6c --- /dev/null +++ b/matchcode/requirements.txt @@ -0,0 +1,89 @@ +asgiref==3.5.2 +attrs==22.1.0 +banal==1.0.6 +beautifulsoup4==4.11.1 +binaryornot==0.4.4 +bitarray==2.6.0 +boolean.py==4.0 +certifi==2022.9.24 +cffi==1.15.1 +chardet==5.1.0 +charset-normalizer==2.1.1 +click==8.1.3 +colorama==0.4.6 +commoncode==31.0.0 +container-inspector==32.0.1 +cryptography==38.0.4 +debian-inspector==31.0.0 +Django==4.1.2 +django-environ==0.9.0 +django-filter==22.1 +djangorestframework==3.14.0 +dockerfile-parse==1.2.0 +dparse2==0.6.1 +extractcode==31.0.0 +extractcode-7z==16.5.210531 +extractcode-libarchive==3.5.1.210531 +fasteners==0.18 +fingerprints==1.0.3 +ftfy==6.1.1 +future==0.18.2 +gemfileparser2==0.9.3 +html5lib==1.1 +idna==3.4 +importlib-metadata==5.1.0 +intbitset==3.0.1 +isodate==0.6.1 +jaraco.functools==3.5.2 +javaproperties==0.8.1 +Jinja2==3.1.2 +jsonstreams==0.6.0 +license-expression==30.0.0 +lxml==4.9.1 +MarkupSafe==2.1.1 +more-itertools==9.0.0 +natsort==8.2.0 +normality==2.4.0 +packagedb==2.0.0 +packageurl-python==0.10.4 +packaging==21.3 +parameter-expansion-patched==0.3.1 +pdfminer.six==20221105 +pefile==2022.5.30 +pip==21.1.2 +pip-requirements-parser==31.2.0 +pkginfo2==30.0.0 +pluggy==1.0.0 +plugincode==31.0.0 +ply==3.11 +psycopg2==2.9.3 +psycopg2-binary==2.9.5 +publicsuffix2==2.20191221 +pyahocorasick==2.0.0b1 +pycparser==2.21 +pygmars==0.8.0 +Pygments==2.13.0 +pymaven-patch==0.3.0 +pyparsing==3.0.9 +pytz==2022.6 +PyYAML==6.0 +rdflib==6.2.0 +requests==2.28.1 +saneyaml==0.5.2 +scancode-toolkit @ https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip +setuptools==57.0.0 +six==1.16.0 +soupsieve==2.3.2.post1 +spdx-tools==0.7.0a3 +sqlparse==0.4.3 +text-unidecode==1.3 +toml==0.10.2 +typecode==30.0.0 +typecode-libmagic==5.39.210531 +urllib3==1.26.13 +urlpy==0.5 +wcwidth==0.2.5 +webencodings==0.5.1 +wheel==0.36.2 +xmltodict==0.13.0 +zipp==3.11.0 diff --git a/matchcode/setup.cfg b/matchcode/setup.cfg new file mode 100644 index 00000000..20ecdec2 --- /dev/null +++ b/matchcode/setup.cfg @@ -0,0 +1,67 @@ +[metadata] +license_files = + LICENSE + AUTHORS.rst + CHANGELOG.rst +name = matchcode +author = nexB. Inc. and others +author_email = info@aboutcode.org +license = nexB Proprietary-license + +# description must be on ONE line https://github.com/pypa/setuptools/issues/1390 +description = MatchCode +long_description = file:README.rst +long_description_content_type = text/x-rst +url = https://github.com/nexB/matchcode + +classifiers = + Intended Audience :: Developers + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Topic :: Utilities + +keywords = + matchcode + +[options] +package_dir = + =src +packages = find: +include_package_data = true +zip_safe = false +install_requires = + bitarray == 2.6.0 + commoncode == 31.0.0 + Django == 4.1.2 + django-environ==0.9.0 + djangorestframework == 3.14.0 + django-filter == 22.1 + packagedb == 2.0.0 + psycopg2 == 2.9.3 + scancode-toolkit == 31.2.2 +setup_requires = setuptools_scm[toml] >= 4 + +python_requires = >=3.8.* + +[options.packages.find] +where = src + +[options.extras_require] +testing = + pytest >= 6, != 7.0.0 + pytest-xdist >= 2 + pytest-django + aboutcode-toolkit >= 6.0.0 + black + +docs= + Sphinx>=3.3.1 + sphinx-rtd-theme>=0.5.0 + doc8>=0.8.1 + +[options.entry_points] +scancode_scan = + halo1 = matchcode.plugin_fingerprint:Halo1Scanner + +scancodeio_pipelines = + matching = matchcode.pipelines.matching:Matching diff --git a/matchcode/setup.py b/matchcode/setup.py new file mode 100644 index 00000000..bac24a43 --- /dev/null +++ b/matchcode/setup.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +import setuptools + +if __name__ == "__main__": + setuptools.setup() diff --git a/matchcode/src/matchcode/__init__.py b/matchcode/src/matchcode/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/matchcode/src/matchcode/api.py b/matchcode/src/matchcode/api.py new file mode 100644 index 00000000..d91da60c --- /dev/null +++ b/matchcode/src/matchcode/api.py @@ -0,0 +1,259 @@ +# +# Copyright (c) nexB Inc. http://www.nexb.com/ - All rights reserved. +# + +from django.db.models import Q +from django.forms import widgets +from django.forms.fields import MultipleChoiceField +from django_filters.filters import MultipleChoiceFilter +from django_filters.rest_framework import FilterSet +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.serializers import CharField +from rest_framework.serializers import ModelSerializer +from rest_framework.serializers import ReadOnlyField +from rest_framework.serializers import Serializer +from rest_framework.viewsets import ReadOnlyModelViewSet + +from matchcode.fingerprinting import create_halohash_chunks +from matchcode.fingerprinting import split_fingerprint +from matchcode.models import ExactFileIndex +from matchcode.models import ExactPackageArchiveIndex +from matchcode.models import ApproximateDirectoryContentIndex +from matchcode.models import ApproximateDirectoryStructureIndex +from matchcode.utils import hexstring_to_binarray + + +class BaseFileIndexSerializer(ModelSerializer): + sha1 = CharField(source='fingerprint') + purl = CharField(source='package.package.package_url') + + +class ExactFileIndexSerializer(BaseFileIndexSerializer): + class Meta: + model = ExactFileIndex + fields = ( + 'sha1', + 'purl' + ) + + +class ExactPackageArchiveIndexSerializer(BaseFileIndexSerializer): + class Meta: + model = ExactPackageArchiveIndex + fields = ( + 'sha1', + 'purl' + ) + + +class BaseDirectoryIndexSerializer(ModelSerializer): + fingerprint = ReadOnlyField() + purl = CharField(source='package.package.package_url') + + +class ApproximateDirectoryContentIndexSerializer(BaseDirectoryIndexSerializer): + class Meta: + model = ApproximateDirectoryContentIndex + fields = ( + 'fingerprint', + 'purl', + ) + + +class ApproximateDirectoryStructureIndexSerializer(BaseDirectoryIndexSerializer): + class Meta: + model = ApproximateDirectoryStructureIndex + fields = ( + 'fingerprint', + 'purl', + ) + + +class BaseDirectoryIndexMatchSerializer(Serializer): + fingerprint = CharField() + matched_fingerprint = CharField() + purl = CharField() + + +class CharMultipleWidget(widgets.TextInput): + """ + Enables the support for `MultiValueDict` `?field=a&field=b` + reusing the `SelectMultiple.value_from_datadict()` but render as a `TextInput`. + Taken from https://github.com/nexB/dejacode/blob/80fded5521f682b554621a8781fec5560d0c1f09/dje/filters.py#L310 + """ + def value_from_datadict(self, data, files, name): + value = widgets.SelectMultiple().value_from_datadict(data, files, name) + if not value or value == ['']: + return '' + + return value + + def format_value(self, value): + """ + Return a value as it should appear when rendered in a template. + """ + return ', '.join(value) + + +class MultipleCharField(MultipleChoiceField): + """ + Overrides `MultipleChoiceField` to fit in `MultipleCharFilter`. + Taken from https://github.com/nexB/dejacode/blob/80fded5521f682b554621a8781fec5560d0c1f09/dje/filters.py#L330 + """ + widget = CharMultipleWidget + + def valid_value(self, value): + return True + + +class MultipleCharFilter(MultipleChoiceFilter): + """ + Filters on multiple values for a CharField type using `?field=a&field=b` URL syntax. + Taken from https://github.com/nexB/dejacode/blob/80fded5521f682b554621a8781fec5560d0c1f09/dje/filters.py#L340 + """ + field_class = MultipleCharField + + +class MultipleSHA1Filter(MultipleCharFilter): + """ + Overrides `MultipleCharFilter.filter()` to convert the SHA1 + into a bytearray so it can be queried + """ + def filter(self, qs, value): + if not value: + return qs + + q = Q() + for val in value: + v = hexstring_to_binarray(val) + q.add(Q(sha1=v), Q.OR) + + return qs.filter(q) + + +class MultipleFingerprintFilter(MultipleCharFilter): + """ + Overrides `MultipleCharFilter.filter()` to process fingerprint from a single + string into multiple values used for querying. + + In the BaseDirectoryIndex model, the fingerprint is stored in four chunks of + equal size, not as a single field that contains the entire fingerprint. We + must process the fingerprint into the correct parts so we can use those + parts to query the different fields. + """ + def filter(self, qs, value): + if not value: + return qs + + q = Q() + for val in value: + indexed_elements_count, bah128 = split_fingerprint(val) + chunk1, chunk2, chunk3, chunk4 = create_halohash_chunks(bah128) + q.add( + Q( + indexed_elements_count=indexed_elements_count, + chunk1=chunk1, + chunk2=chunk2, + chunk3=chunk3, + chunk4=chunk4 + ), + Q.OR + ) + + return qs.filter(q) + + +class BaseFileIndexFilterSet(FilterSet): + sha1 = MultipleSHA1Filter() + + +class ExactFileIndexFilterSet(BaseFileIndexFilterSet): + class Meta: + model = ExactFileIndex + fields = ( + 'sha1', + ) + + +class ExactPackageArchiveFilterSet(BaseFileIndexFilterSet): + class Meta: + model = ExactPackageArchiveIndex + fields = ( + 'sha1', + ) + + +class BaseDirectoryIndexFilterSet(FilterSet): + fingerprint = MultipleFingerprintFilter() + + +class ApproximateDirectoryContentFilterSet(BaseDirectoryIndexFilterSet): + class Meta: + model = ApproximateDirectoryContentIndex + fields = ( + 'fingerprint', + ) + + +class ApproximateDirectoryStructureFilterSet(BaseDirectoryIndexFilterSet): + class Meta: + model = ApproximateDirectoryStructureIndex + fields = ( + 'fingerprint', + ) + + +class BaseFileIndexViewSet(ReadOnlyModelViewSet): + lookup_field = 'sha1' + + +class ExactFileIndexViewSet(BaseFileIndexViewSet): + queryset = ExactFileIndex.objects.all() + serializer_class = ExactFileIndexSerializer + filterset_class = ExactFileIndexFilterSet + + +class ExactPackageArchiveIndexViewSet(BaseFileIndexViewSet): + queryset = ExactPackageArchiveIndex.objects.all() + serializer_class = ExactPackageArchiveIndexSerializer + filterset_class = ExactPackageArchiveFilterSet + + +class BaseDirectoryIndexViewSet(ReadOnlyModelViewSet): + lookup_field = 'fingerprint' + + @action(detail=False) + def match(self, request): + fingerprints = request.query_params.getlist('fingerprint') + if not fingerprints: + return Response() + + model_class = self.get_serializer().Meta.model + results = [] + unique_fingerprints = set(fingerprints) + for fingerprint in unique_fingerprints: + matches = model_class.match(fingerprint) + for match in matches: + results.append( + { + 'fingerprint': fingerprint, + 'matched_fingerprint': match.fingerprint(), + 'purl': match.package.package.package_url, + } + ) + + serialized_match_results = BaseDirectoryIndexMatchSerializer(results, many=True) + return Response(serialized_match_results.data) + + +class ApproximateDirectoryContentIndexViewSet(BaseDirectoryIndexViewSet): + queryset = ApproximateDirectoryContentIndex.objects.all() + serializer_class = ApproximateDirectoryContentIndexSerializer + filterset_class = ApproximateDirectoryContentFilterSet + + +class ApproximateDirectoryStructureIndexViewSet(BaseDirectoryIndexViewSet): + queryset = ApproximateDirectoryStructureIndex.objects.all() + serializer_class = ApproximateDirectoryStructureIndexSerializer + filterset_class = ApproximateDirectoryStructureFilterSet diff --git a/matchcode/src/matchcode/api_custom.py b/matchcode/src/matchcode/api_custom.py new file mode 100644 index 00000000..30b68ff2 --- /dev/null +++ b/matchcode/src/matchcode/api_custom.py @@ -0,0 +1,17 @@ +# +# Copyright (c) nexB Inc. http://www.nexb.com/ - All rights reserved. +# + +from rest_framework.pagination import PageNumberPagination + + +class PageSizePagination(PageNumberPagination): + """ + Adds the page_size parameter. Default results per page is 10. + A page_size parameter can be provided, limited to 100 results per page max. + For example: + http://api.example.org/accounts/?page=4&page_size=100 + """ + page_size = 10 + max_page_size = 100 + page_size_query_param = 'page_size' diff --git a/matchcode/src/matchcode/fingerprinting.py b/matchcode/src/matchcode/fingerprinting.py new file mode 100644 index 00000000..6d8f2795 --- /dev/null +++ b/matchcode/src/matchcode/fingerprinting.py @@ -0,0 +1,105 @@ +# +# Copyright (c) nexB, Inc. http://www.nexb.com/ - All rights reserved. +# + +from matchcode.halohash import BitAverageHaloHash +from matchcode.utils import hexstring_to_binarray + + +def _create_directory_fingerprint(inputs): + """ + Return a 128-bit BitAverageHaloHash fingerprint in hex from `inputs` + """ + inputs = [i.encode('utf-8') for i in inputs if i] + bah128 = BitAverageHaloHash(inputs, size_in_bits=128).hexdigest() + inputs_count = len(inputs) + inputs_count_hex_str = '%08x' % inputs_count + bah128 = bah128.decode('utf-8') + directory_fingerprint = inputs_count_hex_str + bah128 + return directory_fingerprint + + +def create_content_fingerprint(resources): + """ + Collect SHA1 strings from a list of Resources (`resources`) and create a + directory fingerprint from them + """ + features = [r.sha1 for r in resources if r.sha1] + return _create_directory_fingerprint(features) + + +def _get_resource_subpath(resource, top): + """ + Return the subpath of `resource` relative to `top` from `codebase` + + For example: + + top.path = 'foo/bar/' + resource.path = 'foo/bar/baz.c' + + The subpath returned would be 'baz.c' + """ + _, _, subpath = resource.path.partition(top.path) + subpath = subpath.lstrip('/') + return subpath + + +def create_structure_fingerprint(directory, children): + """ + Collect the subpaths of children Resources of Resource `directory` and + create a fingerprint from them + """ + features = [] + for child in children: + if not child.path: + continue + child_subpath = _get_resource_subpath(child, directory) + rounded_child_size = int(child.size / 10) * 10 + path_feature = str(rounded_child_size) + child_subpath + features.append(path_feature) + return _create_directory_fingerprint(features) + + +def compute_directory_fingerprints(codebase): + """ + Compute fingerprints for a directory from `codebase` + """ + for resource in codebase.walk(topdown=False): + if resource.is_file or not resource.path: + continue + children = [r for r in resource.walk(codebase) if r.is_file] + if len(children) == 1: + continue + resource.extra_data['directory_content'] = create_content_fingerprint(children) + resource.extra_data['directory_structure'] = create_structure_fingerprint(resource, children) + resource.save(codebase) + return codebase + + +def split_fingerprint(directory_fingerprint): + """ + Given a string `directory_fingerprint`, return the indexed elements count as + an integer and the bah128 fingerprint string + """ + indexed_elements_count_hash = directory_fingerprint[0:8] + indexed_elements_count = int(indexed_elements_count_hash, 16) + bah128 = directory_fingerprint[8:] + return indexed_elements_count, bah128 + + +def create_halohash_chunks(bah128): + """ + Given a 128-bit bah128 hash string, split it into 4 chunks and return those + chunks as bytearrays + """ + chunk1 = bah128[0:8] + chunk2 = bah128[8:16] + chunk3 = bah128[16:24] + chunk4 = bah128[24:32] + + chunk1 = hexstring_to_binarray(chunk1) + chunk2 = hexstring_to_binarray(chunk2) + chunk3 = hexstring_to_binarray(chunk3) + chunk4 = hexstring_to_binarray(chunk4) + + return chunk1, chunk2, chunk3, chunk4 diff --git a/matchcode/src/matchcode/halohash.py b/matchcode/src/matchcode/halohash.py new file mode 100644 index 00000000..0fcb0d52 --- /dev/null +++ b/matchcode/src/matchcode/halohash.py @@ -0,0 +1,406 @@ +# +# Copyright (c) 2017 nexB Inc. and others. All rights reserved. +# http://nexb.com and https://github.com/nexB/scancode-toolkit/ +# The ScanCode software is licensed under the Apache License version 2.0. +# Data generated with ScanCode require an acknowledgment. +# ScanCode is a trademark of nexB Inc. +# +# You may not use this software except in compliance with the License. +# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# +# When you publish or redistribute any data created with ScanCode or any ScanCode +# derivative work, you must accompany this data with the following acknowledgment: +# +# Generated with ScanCode and provided on an "AS IS" BASIS, WITHOUT WARRANTIES +# OR CONDITIONS OF ANY KIND, either express or implied. No content created from +# ScanCode should be considered or used as legal advice. Consult an Attorney +# for any legal advice. +# ScanCode is a free software code scanning tool from nexB Inc. and others. +# Visit https://github.com/nexB/scancode-toolkit/ for support and download. + +import binascii + +from bitarray import bitarray +from bitarray.util import count_xor + +from commoncode import codec + +from matchcode import hash as commoncode_hash + +""" +Halo is a family of hash functions that have the un-common property that mostly +similar -- but not identical -- inputs will hash to very similar outputs. This +type of hash function is sometimes called a locality-sensitive hash function, +because it is sensitive to the locality of the data being hashed. + +The purpose of these hashes is to quickly compare a large number of elements +that are likely to be similar to find candidates and then compute a more +comprehensive similarity only on the candidates. This includes goals such as +identifying near-duplicates of things or to group very similar things together +(a.k.a. clustering), as well as to detect similarities between inputs or perform +quick comparisons under a certain threshold. + +For a traditional 'good' hash function, small changes in the input will yield +very different hash outputs (through diffusion and avalanche effect). For +instance, cryptographic hashes such as SHA1 or MD5 behave this way. If you hash +two bit strings with a SHA1 function and there is only one bit of difference +between these two strings then the resulting hashes will be rather different. On +average, each time one bit is added to the input, good hash functions have half +of the output bits switched from 0 to 1. + +A Halo hash instead hashes similar inputs to the same hash or to a hash that +differs only by a few bits. The similarity between two hashes becomes an +approximation of the similarity between the two original inputs. This simalirity +is computed using the hamming distance or number of non-matching bits between +two hashes outputs bit straings. This hamming distance is roughly proportional +to the similarity between the two original inputs and can be used to estimate +the similarity of inputs without having access to these full input. + +The Halo name is a play on what one of the hashing function does: a halo is like +a fuzzy, halo'ish representation of the input. + +The bit average function ressembles Charikar's algorithm by using each bits in an +array of hashes but does not use a TF/IDF resulting in a simpler procedure. +""" + + +class BitAverageHaloHash(object): + """ + A bit matrix averaging hash. + + The high level processing sketch looks like this: + For an input of: + ['this' ,'is', 'a', 'rose', 'great']: + + * we first hash each list item to get something like + [4, 15, 2, 12, 12] (for instance with a very short hash function of 4 bits output) + + or as bits this would be something like this: + + ['0011', + '1110', + '0010', + '1100', + '1100'] + + * we sum up each bit positions/columns together: + ['0011', + '1110', + '0010', + '1100', + '1100'] + ------- + 3331 + + or stated otherwise: pos1=3, pos2=3, pos3=3, pos4=1 + + * The mean value for a column is number of hashes/2 (2 because we use bits). + Here mean = 5 hashes/2 = 2.5 + + * We compare the sum of each position with the mean and yield a bit: + if pos sum > mean yield 1 else yield 0 + position1 = 3 > mean = 2.5 , then bit=1 + position2 = 3 > mean = 2.5 , then bit=1 + position3 = 3 > mean = 2.5 , then bit=1 + position4 = 1 < mean = 2.5 , then bit=0 + + * We build a hash by concatenating the resulting bits: + pos 1 + pos2 + pos3 + pos4 = '1110' + + In general, this hash seems to show a lower accuracy and higher sensitivity + with small string and small inputs variations than the bucket average hash. + But it works better on shorter inputs. + + Some usage examples: + + >>> z = b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible for intVal'''.split() + >>> a = BitAverageHaloHash(z, size_in_bits=256) + >>> len(a.digest()) + 32 + >>> z = b'''The value specified for size must be no + ... more larger than the smallest bit vector possible for intVal'''.split() + >>> b = BitAverageHaloHash(z, size_in_bits=256) + >>> a.distance(b) + 57 + >>> b.distance(a) + 57 + >>> a = BitAverageHaloHash(size_in_bits=160) + >>> z = [a.update(x) for x in b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible for intVal'''.split()] + >>> assert a.hexdigest() == b'2c10223104c43470e10b1157e6415b2f730057d0' + >>> b = BitAverageHaloHash(size_in_bits=160) + >>> z = [b.update(x) for x in b'''The value specified for size must be no + ... more larger than the smallest bit vector possible for intVal'''.split()] + >>> assert b.hexdigest() == b'2c912433c4c624e0b03b34576641df8fe00017d0' + >>> a.distance(b) + 29 + >>> a = BitAverageHaloHash(size_in_bits=128) + >>> z =[a.update(x) for x in b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible for intVal'''.split()] + >>> assert a.hexdigest() == b'028b1699c0c5310cd1b566a893d12f10' + >>> b = BitAverageHaloHash(size_in_bits=128) + >>> z = [b.update(x) for x in b'''The value specified for size must be no + ... more larger than the smallest bit vector possible for intVal'''.split()] + >>> assert b.hexdigest() == b'0002969060d5b344d1b7602cd9e127b0' + >>> a.distance(b) + 27 + >>> a = BitAverageHaloHash(size_in_bits=64) + >>> z = [a.update(x) for x in b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible for intVal'''.split()] + >>> assert a.hexdigest() == b'028b1699c0c5310c' + >>> b = BitAverageHaloHash(size_in_bits=64) + >>> z = [b.update(x) for x in b'''The value specified for size must be no + ... more larger than the smallest bit vector possible for intVal'''.split()] + >>> assert b.hexdigest() == b'0002969060d5b344' + >>> a.distance(b) + 14 + >>> a = BitAverageHaloHash(size_in_bits=32) + >>> z = [a.update(x) for x in b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible for intVal'''.split()] + >>> b = BitAverageHaloHash(size_in_bits=32) + >>> z = [b.update(x) for x in b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible by intVal'''.split()] + >>> a.distance(b) + 5 + >>> a = BitAverageHaloHash(size_in_bits=512) + >>> z = [a.update(x) for x in b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible for intVal'''.split()] + >>> b = BitAverageHaloHash(size_in_bits=512) + >>> z = [b.update(x) for x in b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible by intVal'''.split()] + >>> a.distance(b) + 46 + """ + + # TODO: Keep state, keep 1 position per column + + # TODO: create method to aggregate multiple BitAverageHaloHashes together + # TODO: refactor this, don't keep all hashes + # TODO: keep only a list of columns + def __init__(self, msg=None, size_in_bits=128): + self.size_in_bits = size_in_bits + self.columns = [0] * size_in_bits + + # TODO: pick one hash module instead of selecting from multiple hash modules + self.hashmodule = lambda x: x + try: + # TODO: pick one hash algorithm + self.hashmodule = commoncode_hash.get_hasher(size_in_bits) + except: + raise Exception('No available hash module for the requested ' + 'hash size in bits: %(size_in_bits)d' % locals()) + self.update(msg) + + @property + def digest_size(self): + return self.size_in_bits // 8 + + def update(self, msg): + """ + Append a bytestring or sequence of bytestrings to the hash. + """ + if not msg: + return + if isinstance(msg, (list, tuple,)): + for m in msg: + self.__hashup(m) + else: + self.__hashup(msg) + + def __hashup(self, msg): + assert isinstance(msg, bytes) + hsh = self.hashmodule(msg).digest() + bits = bitarray_from_bytes(hsh) + normalized = (-1 if v else 1 for v in bits) + for i, column in enumerate(normalized): + self.columns[i] += column + + def hexdigest(self): + """ + Return the hex-encoded hash value. + """ + return binascii.hexlify(self.digest()) + + def b64digest(self): + """ + Return a base64 "url safe"-encoded string representing this hash. + """ + return codec.b64encode(self.digest()) + + def digest(self): + """ + Return a binary string representing this hash. + """ + flattened = [1 if col > 0 else 0 for col in self.columns] + bits = bitarray(flattened) + return bits.tobytes() + + def distance(self, other): + """ + Return the bit Hamming distance between this hash and another hash. + """ + return int(count_xor(self.hash(), other.hash())) + + def hash(self): + return bitarray_from_bytes(self.digest()) + + @classmethod + def combine(cls, hashes): + """ + Return a BitAverageHaloHash by summing and averaging the columns of the + BitAverageHaloHashes in `hashes` together, putting the resulting + columns into a new BitAverageHaloHash and returning it + """ + size_in_bits = hashes[0].size_in_bits + for h in hashes: + assert isinstance(hash, cls), 'all hashes should be a BitAverageHaloHash, not {}'.format(type(h)) + assert h.size_in_bits == size_in_bits + + all_columns = [h.columns for h in hashes] + b = cls() + b.columns = [sum(col) for col in zip(*all_columns)] + return b + + +def bitarray_from_bytes(b): + """ + Return a bitarray built from a byte string b. + """ + a = bitarray() + a.frombytes(b) + return a + + +def byte_hamming_distance(b1, b2): + b1 = binascii.unhexlify(b1) + b2 = binascii.unhexlify(b2) + b1 = bitarray_from_bytes(b1) + b2 = bitarray_from_bytes(b2) + return hamming_distance(b1, b2) + + +def hamming_distance(bv1, bv2): + """ + Return the Hamming distance between `bv1` and `bv2` bitvectors as the + number of equal bits for all positions. (e.g. the count of bits set to one + in an XOR between two bit strings.) + + `bv1` and `bv2` must both be either hash-like Halohash instances (with a + hash() function) or bit array instances (that can be manipulated as-is). + + See http://en.wikipedia.org/wiki/Hamming_distance + + For example: + + >>> b1 = bitarray('0001010111100001111') + >>> b2 = bitarray('0001010111100001111') + >>> hamming_distance(b1, b2) + 0 + >>> b1 = bitarray('11110000') + >>> b2 = bitarray('00001111') + >>> hamming_distance(b1, b2) + 8 + >>> b1 = bitarray('11110000') + >>> b2 = bitarray('00110011') + >>> hamming_distance(b1, b2) + 4 + """ + return int(count_xor(bv1, bv2)) + + +def slices(s, size): + """ + Given a sequence s, return a sequence of non-overlapping slices of `size`. + Raise an AssertionError if the sequence length is not a multiple of `size`. + + For example: + >>> slices([1, 2, 3, 4, 5, 6], 2) + [(1, 2), (3, 4), (5, 6)] + >>> slices([1, 2, 3, 4, 5, 6], 3) + [(1, 2, 3), (4, 5, 6)] + >>> try: + ... slices([1, 2, 3, 4, 5, 6], 4) + ... except AssertionError: + ... pass + """ + length = len(s) + assert length % size == 0, 'Invalid slice size: len(%(s)r) is not a multiple of %(size)r' % locals() + # TODO: time alternative + # return [s[index:index + size] for index in range(0, length, size)] + chunks = [iter(s)] * size + return list(zip(*chunks)) + + +def common_chunks_from_hexdigest(h1, h2, chunk_bytes_length=4): + """ + Compute the number of common chunks of byte length `chunk_bytes_length` between two + strings h1 and h2, each representing a BitAverageHaloHash hexdigest value. + + For example: + + >>> a = '1f22c2c871cd70521211b138cd76fc04' + >>> b = '1f22c2c871cd7852121bbd38c576bc84' + >>> common_chunks_from_hexdigest(a, b, 32) + 1 + + Note: `a` and `b` start with the same 8 characters, where the next groups + of 8 have a few characters off + + >>> byte_hamming_distance(a, b) + 8 + """ + h1 = bitarray_from_bytes(bytes(binascii.unhexlify(h1))) + h2 = bitarray_from_bytes(bytes(binascii.unhexlify(h2))) + h1_slices = slices(h1, chunk_bytes_length) + h2_slices = slices(h2, chunk_bytes_length) + commons = (1 for h1s, h2s in zip(h1_slices, h2_slices) if h1s == h2s) + return sum(commons) + + +def common_chunks(h1, h2, chunk_bytes_length=4): + """ + Compute the number of common chunks of byte length `chunk_bytes_length` between to + hashes h1 and h2 using the digest. + + Note that chunks that are all set to zeroes are matched too: they are be + significant such as empty buckets of bucket hashes. + + For example: + + >>> m1 = b'The value specified for size must be at least as large'.split() + >>> m2 = b'The value specific for size must be at least as large'.split() + >>> a = BitAverageHaloHash(msg=m1, size_in_bits=256) + >>> b = BitAverageHaloHash(msg=m2, size_in_bits=256) + >>> common_chunks(a, b, 2) + 1 + >>> byte_hamming_distance(a.hexdigest(), b.hexdigest()) + 32 + """ + h1_slices = slices(h1.digest(), chunk_bytes_length) + h2_slices = slices(h2.digest(), chunk_bytes_length) + commons = (1 for h1s, h2s in zip(h1_slices, h2_slices) if h1s == h2s) + return sum(commons) + + +def bit_to_num(bits): + """ + Return an int (or long) for a bit array. + + For example: + TODO: test + """ + return int(bits.to01(), 2) + + +# TODO: add test! +def decode_vector(b64_str): + """ + Return a bit array from an encoded string representation. + """ + decoded = codec.urlsafe_b64decode(b64_str) + return bitarray_from_bytes(decoded) diff --git a/matchcode/src/matchcode/hash.py b/matchcode/src/matchcode/hash.py new file mode 100644 index 00000000..24c1429c --- /dev/null +++ b/matchcode/src/matchcode/hash.py @@ -0,0 +1,131 @@ +# +# Copyright (c) 2017 nexB Inc. and others. All rights reserved. +# http://nexb.com and https://github.com/nexB/scancode-toolkit/ +# The ScanCode software is licensed under the Apache License version 2.0. +# Data generated with ScanCode require an acknowledgment. +# ScanCode is a trademark of nexB Inc. +# +# You may not use this software except in compliance with the License. +# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# +# When you publish or redistribute any data created with ScanCode or any ScanCode +# derivative work, you must accompany this data with the following acknowledgment: +# +# Generated with ScanCode and provided on an "AS IS" BASIS, WITHOUT WARRANTIES +# OR CONDITIONS OF ANY KIND, either express or implied. No content created from +# ScanCode should be considered or used as legal advice. Consult an Attorney +# for any legal advice. +# ScanCode is a free software code scanning tool from nexB Inc. and others. +# Visit https://github.com/nexB/scancode-toolkit/ for support and download. + +# From https://github.com/nexB/scancode-toolkit-contrib + +import hashlib + +from commoncode.codec import bin_to_num +from commoncode.codec import urlsafe_b64encode +from commoncode import filetype + +""" +Hashes and checksums. + +Low level hash functions using standard crypto hashes used to construct hashes +of various lengths. Hashes that are smaller than 128 bits are based on a +truncated md5. Other length use SHA hashes. + +Checksums are operating on files. +""" + + +def _hash_mod(bitsize, hmodule): + """ + Return a hashing class returning hashes with a `bitsize` bit length. The + interface of this class is similar to the hash module API. + """ + + class hasher(object): + + def __init__(self, msg=None): + self.digest_size = bitsize // 8 + self.h = msg and hmodule(msg).digest()[:self.digest_size] or None + + def digest(self): + return self.h + + def hexdigest(self): + return self.h and self.h.encode('hex') + + def b64digest(self): + return self.h and urlsafe_b64encode(self.h) + + def intdigest(self): + return self.h and bin_to_num(self.h) + + return hasher + + +# Base hashers for each bit size +_hashmodules_by_bitsize = { + # md5-based + 16: _hash_mod(16, hashlib.md5), + 32: _hash_mod(32, hashlib.md5), + 64: _hash_mod(64, hashlib.md5), + 128: _hash_mod(128, hashlib.md5), + # sha-based + 160: _hash_mod(160, hashlib.sha1), + 256: _hash_mod(256, hashlib.sha256), + 384: _hash_mod(384, hashlib.sha384), + 512: _hash_mod(512, hashlib.sha512) +} + + +def get_hasher(bitsize): + """ + Return a hasher for a given size in bits of the resulting hash. + """ + return _hashmodules_by_bitsize[bitsize] + + +def checksum(location, bitsize, base64=False): + """ + Return a checksum of `bitsize` length from the content of the file at + `location`. The checksum is a hexdigest or base64-encoded is `base64` is + True. + """ + if not filetype.is_file(location): + return + hasher = get_hasher(bitsize) + + # fixme: we should read in chunks + with open(location, 'rb') as f: + hashable = f.read() + + hashed = hasher(hashable) + if base64: + return hashed.b64digest() + + return hashed.hexdigest() + + +def md5(location): + return checksum(location, bitsize=128, base64=False) + + +def sha1(location): + return checksum(location, bitsize=160, base64=False) + + +def b64sha1(location): + return checksum(location, bitsize=160, base64=True) + + +def sha256(location): + return checksum(location, bitsize=256, base64=False) + + +def sha512(location): + return checksum(location, bitsize=512, base64=False) diff --git a/matchcode/src/matchcode/indexing.py b/matchcode/src/matchcode/indexing.py new file mode 100644 index 00000000..9d6ead31 --- /dev/null +++ b/matchcode/src/matchcode/indexing.py @@ -0,0 +1,155 @@ +# +# Copyright (c) 2020 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# + +import logging +import os +import sys + +from commoncode.resource import VirtualCodebase + +from matchcode.fingerprinting import compute_directory_fingerprints +from matchcode.models import ApproximateDirectoryContentIndex +from matchcode.models import ApproximateDirectoryStructureIndex +from matchcode.models import get_or_create_indexable_package +from matchcode.models import ExactPackageArchiveIndex +from matchcode.models import ExactFileIndex + + +TRACE = False + +logger = logging.getLogger(__name__) +logging.basicConfig(stream=sys.stdout) +logger.setLevel(logging.INFO) + + +def index_package_archives(indexable_package): + """ + Index Package archives for matching + + Return True if an ExactPackageArchiveIndex has been created, + otherwise return False + """ + package_data = indexable_package.package + package_sha1 = package_data.sha1 + _, created = ExactPackageArchiveIndex.index( + sha1=package_sha1, + indexable_package=indexable_package, + ) + return created + + +def index_package_file(resource): + """ + Index Package files for matching + + Return a tuple of booleans, `created_exact_file_index` and `created_indexable_package`. + `created_exact_file_index` returns True if it has been created, False otherwise. + The same is true with `created_indexable_package` + """ + indexable_package, created_indexable_package = get_or_create_indexable_package(resource.package) + _, created_exact_file_index = ExactFileIndex.index( + sha1=resource.sha1, + indexable_package=indexable_package + ) + return created_exact_file_index, created_indexable_package + + +def _create_virtual_codebase_from_indexable_package(indexable_package): + """ + Return a VirtualCodebase from the resources of `indexable_package` + """ + # Create something that looks like a scancode scan so we can import it into a VirtualCodebase + # TODO: Evolve this into something more elaborate, e.g. + # Codebase class methods can manipulate Resource table entries + package_resources = indexable_package.resources.order_by('path') + if not package_resources: + return + + files = [] + for resource in package_resources: + files.append( + { + 'path': resource.path, + 'size': resource.size, + 'sha1': resource.sha1, + 'md5': resource.md5, + 'type': resource.type, + } + ) + + make_new_root = False + sample_file_path = files[0].get('path', '') + root_dir = sample_file_path.split('/')[0] + for f in files: + file_path = f.get('path', '') + if not file_path.startswith(root_dir): + make_new_root = True + break + + if make_new_root: + package = indexable_package.package + new_root = '{}-{}'.format(package.name, package.version) + for f in files: + new_path = os.path.join(new_root, f.get('path', '')) + f['path'] = new_path + + # Create VirtualCodebase + mock_scan = dict(files=files) + return VirtualCodebase(location=mock_scan) + + +def index_directory_fingerprints(codebase, indexable_package): + """ + Compute fingerprints for a directory from `codebase` and index them to + ApproximateDirectoryContentIndex and ApproximateDirectoryStructureIndex + + Return a tuple of integers, `indexed_adci` and `indexed_adsi`, that + represent the number of indexed ApproximateDirectoryContentIndex and + ApproximateDirectoryStructureIndex created, respectivly. + """ + indexed_adci = 0 + indexed_adsi = 0 + for resource in codebase.walk(topdown=False): + directory_content_fingerprint = resource.extra_data.get('directory_content', '') + directory_structure_fingerprint = resource.extra_data.get('directory_structure', '') + + if directory_content_fingerprint: + _, adci_created = ApproximateDirectoryContentIndex.index( + directory_fingerprint=directory_content_fingerprint, + resource_path=resource.path, + indexable_package=indexable_package, + ) + if adci_created: + indexed_adci += 1 + + if directory_structure_fingerprint: + _, adsi_created = ApproximateDirectoryStructureIndex.index( + directory_fingerprint=directory_structure_fingerprint, + resource_path=resource.path, + indexable_package=indexable_package, + ) + if adsi_created: + indexed_adsi += 1 + + return indexed_adci, indexed_adsi + + +def index_package_directories(indexable_package): + """ + Index the directories of `indexable_package` to + ApproximateDirectoryContentIndex and ApproximateDirectoryStructureIndex + + Return a tuple of integers, `indexed_adci` and `indexed_adsi`, that + represent the number of indexed ApproximateDirectoryContentIndex and + ApproximateDirectoryStructureIndex created, respectivly. + + Return 0, 0 if a VirtualCodebase cannot be created from the Resources of an + IndexablePackage + """ + vc = _create_virtual_codebase_from_indexable_package(indexable_package) + if not vc: + return 0, 0 + + vc = compute_directory_fingerprints(vc) + return index_directory_fingerprints(vc, indexable_package) diff --git a/matchcode/src/matchcode/management/__init__.py b/matchcode/src/matchcode/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/matchcode/src/matchcode/management/commands/__init__.py b/matchcode/src/matchcode/management/commands/__init__.py new file mode 100644 index 00000000..d4ee6844 --- /dev/null +++ b/matchcode/src/matchcode/management/commands/__init__.py @@ -0,0 +1,32 @@ +# +# Copyright (c) 2017 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# + +import logging + +from django.core.management.base import BaseCommand + + +class VerboseCommand(BaseCommand): + """ + Base verbosity-aware Command. + Command modules should define logging and subclasses should call + logger.setLevel(self.get_verbosity(**options)) + in their handle() method. + """ + + def get_verbosity(self, **options): + verbosity = int(options.get('verbosity', 1)) + levels = {1: logging.INFO, 2: logging.ERROR, 3: logging.DEBUG} + return levels.get(verbosity, logging.CRITICAL) + + MUST_STOP = False + + @classmethod + def stop_handler(cls, *args, **kwargs): + """ + Signal handler use to support a graceful exit when flag is to True. + Subclasses must create this signal to use this: + signal.signal(signal.SIGTERM, Command.stop_handler) + """ + cls.MUST_STOP = True diff --git a/matchcode/src/matchcode/management/commands/index_packages.py b/matchcode/src/matchcode/management/commands/index_packages.py new file mode 100644 index 00000000..88b07d1e --- /dev/null +++ b/matchcode/src/matchcode/management/commands/index_packages.py @@ -0,0 +1,78 @@ +# +# Copyright (c) 2020 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# + +from datetime import datetime +import logging +import sys +import time + +from django.db import transaction + +from matchcode.indexing import index_package_archives +from matchcode.indexing import index_package_directories +from matchcode.indexing import index_package_file +from matchcode.management.commands import VerboseCommand +from matchcode.models import get_or_create_indexable_package +from matchcode.models import IndexablePackage +from packagedb.models import Package +from packagedb.models import Resource + + +TRACE = False + +logger = logging.getLogger(__name__) +logging.basicConfig(stream=sys.stdout) +logger.setLevel(logging.INFO) + + +class Command(VerboseCommand): + help = 'Index all Package SHA1 from PackageDB.' + + def handle(self, *args, **options): + # Stats to keep track of during indexing + total_indexable_packages_created = 0 + total_indexed_package_archives = 0 + total_indexed_package_files = 0 + total_indexed_adci = 0 + total_indexed_adsi = 0 + + logger.setLevel(self.get_verbosity(**options)) + start = time.time() + + packages = Package.objects.filter(sha1__isnull=False) + for package in packages.iterator(): + with transaction.atomic(): + indexable_package, created_indexable_package = get_or_create_indexable_package(package) + if created_indexable_package: + total_indexable_packages_created += 1 + created_package_archive = index_package_archives(indexable_package) + if created_package_archive: + total_indexed_package_archives += 1 + + resources = Resource.objects.filter(sha1__isnull=False) + for resource in resources.iterator(): + with transaction.atomic(): + created_package_file, created_indexable_package = index_package_file(resource) + if created_package_file: + total_indexed_package_files += 1 + if created_indexable_package: + total_indexable_packages_created += 1 + + indexable_packages = IndexablePackage.objects.all() + for indexable_package in indexable_packages.iterator(): + with transaction.atomic(): + indexed_adci, indexed_adsi = index_package_directories(indexable_package) + total_indexed_adci += indexed_adci + total_indexed_adsi += indexed_adsi + + # TODO: Format this better for viewing on terminal + print('Package indexing completed at: {}'.format(datetime.utcnow().isoformat())) + total_duration = int(time.time() - start) + print('Total run duration: {} seconds'.format(total_duration)) + print('Created:') + print('IndexablePackages: {}'.format(total_indexable_packages_created)) + print('ExactPackageArchiveIndex: {}'.format(total_indexed_package_archives)) + print('ExactFileIndex: {}'.format(total_indexed_package_files)) + print('ApproximateDirectoryContentIndex: {}'.format(total_indexed_adci)) + print('ApproximateDirectoryStructureIndex: {}'.format(total_indexed_adsi)) diff --git a/matchcode/src/matchcode/management/commands/match_scan.py b/matchcode/src/matchcode/management/commands/match_scan.py new file mode 100644 index 00000000..e0ca2558 --- /dev/null +++ b/matchcode/src/matchcode/management/commands/match_scan.py @@ -0,0 +1,31 @@ +# +# Copyright (c) 2016 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# + +import json + +from django.core.management.base import BaseCommand + +from matchcode import match + + +class Command(BaseCommand): + help = 'matches packages in a scancode fileinfo scan' + + def add_arguments(self, parser): + parser.add_argument('scancode_file_path', type=str) + parser.add_argument('outfile_path', type=str) + + def handle(self, *args, **options): + scancode_file = options['scancode_file_path'] + outfile = options['outfile_path'] + + # load up the scancode fileinfo json + with open(scancode_file) as f: + scan = json.load(f) + + results = match.match_packages(scan) + + # write new json results + with open(outfile, 'w') as f: + json.dump(results, f, indent=2) diff --git a/matchcode/src/matchcode/match.py b/matchcode/src/matchcode/match.py new file mode 100644 index 00000000..ab3a2df9 --- /dev/null +++ b/matchcode/src/matchcode/match.py @@ -0,0 +1,222 @@ +# +# Copyright (c) 2020 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# + +from functools import reduce +from operator import or_ + +from django.db.models import Q + +from matchcode.models import ApproximateDirectoryContentIndex +from matchcode.models import ApproximateDirectoryStructureIndex +from matchcode.models import ExactFileIndex +from matchcode.models import ExactPackageArchiveIndex + + +# TODO: Refactor this file into functions/utilities used in +# a scanpipe pipeline. +EXACT_PACKAGE_ARCHIVE_MATCH = 0 +APPROXIMATE_DIRECTORY_STRUCTURE_MATCH = 1 +APPROXIMATE_DIRECTORY_CONTENT_MATCH = 2 +EXACT_FILE_MATCH = 3 + + +def get_matchers(): + MATCHERS_BY_MATCH_TYPE = { + EXACT_PACKAGE_ARCHIVE_MATCH: package_archive_match, + APPROXIMATE_DIRECTORY_CONTENT_MATCH: approximate_directory_content_match, + APPROXIMATE_DIRECTORY_STRUCTURE_MATCH: approximate_directory_structure_match, + EXACT_FILE_MATCH: individual_file_match, + } + return MATCHERS_BY_MATCH_TYPE + + +def do_match(codebase, match_type): + """ + Perform Package matching on `codebase` by running matching functions of `match_type` on `codebase` + + The total number of matches found is returned + """ + + matcher = get_matchers().get(match_type) + if not matcher: + raise Exception('Unknown match type: {}'.format(match_type)) + match_count = matcher(codebase) + return match_count + + +def package_archive_match(codebase): + """ + Update Matches from detected Package Archives in `codebase` + + Return the number of matches found in `codebase` + """ + match_count = 0 + for resource in codebase.walk(topdown=True): + if (resource.is_dir + or not resource.is_archive + or resource.extra_data.get('matched', False)): + continue + + archive_matches, match_type = get_archive_match(resource) + if not archive_matches: + continue + + match_count += len(archive_matches) + + # Tag matched Resource as `matched` as to not analyze it later + tag_matched_resources(resource, codebase, archive_matches, match_type) + return match_count + + +def approximate_directory_content_match(codebase): + """ + Update Matches from detected Package directories based on directory contents in `codebase` + + Return the number of matches found in `codebase` + """ + match_count = 0 + for resource in codebase.walk(topdown=True): + if resource.is_file or resource.extra_data.get('matched', False): + continue + + directory_matches, match_type = get_directory_content_match(resource) + if not directory_matches: + continue + + match_count += directory_matches.count() + tag_matched_resources(resource, codebase, directory_matches, match_type) + return match_count + + +def approximate_directory_structure_match(codebase): + """ + Update Matches from detected Package directories based on directory structure in `codebase` + + Return the number of matches found in `codebase` + """ + match_count = 0 + for resource in codebase.walk(topdown=True): + if resource.is_file or resource.extra_data.get('matched', False): + continue + + directory_matches, match_type = get_directory_structure_match(resource) + if not directory_matches: + continue + + match_count += directory_matches.count() + tag_matched_resources(resource, codebase, directory_matches, match_type) + return match_count + + +def individual_file_match(codebase): + """ + Update Matches from detected Package files in `codebase` + + Return the number of matches found in `codebase` + """ + match_count = 0 + for resource in codebase.walk(topdown=True): + if resource.is_dir or resource.extra_data.get('matched', False): + continue + + file_matches, match_type = get_file_match(resource) + if not file_matches: + continue + + match_count += len(file_matches) + tag_matched_resources(resource, codebase, file_matches, match_type) + return match_count + + +def get_directory_content_match(resource): + """ + Match a directory to a Package using its contents + """ + directory_content_fingerprint = resource.extra_data.get('directory_content', '') + matches = ApproximateDirectoryContentIndex.objects.none() + match_type = '' + if directory_content_fingerprint: + directory_matches = ApproximateDirectoryContentIndex.match(directory_content_fingerprint) + matches |= directory_matches + match_type = 'approximate-content' + return matches, match_type + + +# TODO: rename match_directory_structure +def get_directory_structure_match(resource): + """ + Match a directory to a Package using its structure + """ + directory_structure_fingerprint = resource.extra_data.get('directory_structure', '') + matches = ApproximateDirectoryStructureIndex.objects.none() + match_type = '' + if directory_structure_fingerprint: + directory_matches = ApproximateDirectoryStructureIndex.match(directory_structure_fingerprint) + matches |= directory_matches + match_type = 'approximate-structure' + return matches, match_type + + +def get_archive_match(resource): + """ + Match an Archive resource to a Package + """ + file_matches = ExactPackageArchiveIndex.match(resource.sha1) + return file_matches, 'exact-archive' + + +def get_file_match(resource): + """ + Match an individual file back to the Package it is from + """ + file_matches = ExactFileIndex.match(resource.sha1) + return file_matches, 'exact-file' + + +def tag_matched_resource(resource, codebase, purl): + """ + Set a resource to be flagged as matched, so it will not be considered in + subsequent matches once it has been matched + """ + if purl not in resource.matched_to: + resource.matched_to.append(purl) + resource.extra_data['matched'] = True + resource.save(codebase) + + +def tag_matched_resources(resource, codebase, matches, match_type): + """ + Tag this directory and other Resources under this directory so they are not + candidates for matching by checking to see if a Resource path from + `resource` or its children exists in the matched packages in `matches` + """ + for match in matches: + # Prep matched package data and append to `codebase` + matched_package_info = match.package.package.to_dict() + matched_package_info['match_type'] = match_type + codebase.attributes.matches.append(matched_package_info) + + purl = match.package.package.package_url + # Tag the Resource where we found a match + tag_matched_resource(resource, codebase, purl) + + # Find matching package child path for `resource` by creating all possible + # path suffixes from `child.path`, chaining them in Q objects (joined + # by or), then querying the matched packages resources to see if any of + # those suffixes match a package child resource path + for child in resource.walk(codebase): + query = reduce(or_, (Q(path=suffix) for suffix in path_suffixes(child.path)), Q()) + matched_child_resources = match.package.resources.filter(query) + if len(matched_child_resources) > 0: + tag_matched_resource(child, codebase, purl) + + +def path_suffixes(path): + """ + Yield all the suffixes of `path`, starting from the longest (e.g. more segments). + """ + segments = path.strip('/').split('/') + suffixes = (segments[i:] for i in range(len(segments))) + for suffix in suffixes: + yield '/'.join(suffix) diff --git a/matchcode/src/matchcode/migrations/0001_initial.py b/matchcode/src/matchcode/migrations/0001_initial.py new file mode 100644 index 00000000..8acf39f3 --- /dev/null +++ b/matchcode/src/matchcode/migrations/0001_initial.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.17 on 2021-03-25 23:54 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='ApproximateDirectoryContentIndex', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('indexed_elements_count', models.IntegerField(help_text='Number of elements that went into the fingerprint')), + ('chunk1', models.BinaryField(db_index=True, help_text='Binary form of the first 8 (0-7) hex digits of the fingerprint', max_length=4)), + ('chunk2', models.BinaryField(db_index=True, help_text='Binary form of the second 8 (8-15) hex digits of the fingerprint', max_length=4)), + ('chunk3', models.BinaryField(db_index=True, help_text='Binary form of the third 8 (16-23) hex digits of the fingerprint', max_length=4)), + ('chunk4', models.BinaryField(db_index=True, help_text='Binary form of the fourth 8 (24-32) hex digits of the fingerprint', max_length=4)), + ('path', models.CharField(help_text='The full path value of this directory', max_length=2000)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ApproximateDirectoryStructureIndex', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('indexed_elements_count', models.IntegerField(help_text='Number of elements that went into the fingerprint')), + ('chunk1', models.BinaryField(db_index=True, help_text='Binary form of the first 8 (0-7) hex digits of the fingerprint', max_length=4)), + ('chunk2', models.BinaryField(db_index=True, help_text='Binary form of the second 8 (8-15) hex digits of the fingerprint', max_length=4)), + ('chunk3', models.BinaryField(db_index=True, help_text='Binary form of the third 8 (16-23) hex digits of the fingerprint', max_length=4)), + ('chunk4', models.BinaryField(db_index=True, help_text='Binary form of the fourth 8 (24-32) hex digits of the fingerprint', max_length=4)), + ('path', models.CharField(help_text='The full path value of this directory', max_length=2000)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ExactFileIndex', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('sha1', models.BinaryField(db_index=True, help_text='Binary form of a SHA1 checksum in lowercase hex for a file', max_length=20)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ExactPackageArchiveIndex', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('sha1', models.BinaryField(db_index=True, help_text='Binary form of a SHA1 checksum in lowercase hex for a file', max_length=20)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='IndexablePackage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(db_index=True, help_text='The UUID of a Package on an instance of PackageDB', unique=True, verbose_name='UUID')), + ('last_indexed_date', models.DateTimeField(help_text='Timestamp set to the date of the last indexing. Used to track indexing status.')), + ('index_error', models.TextField(blank=True, help_text='Indexing errors messages. When present this means the indexing has failed.', null=True)), + ], + ), + migrations.AddField( + model_name='exactpackagearchiveindex', + name='package', + field=models.ForeignKey(help_text='The Package that this file is from', on_delete=django.db.models.deletion.CASCADE, to='matchcode.IndexablePackage'), + ), + migrations.AddField( + model_name='exactfileindex', + name='package', + field=models.ForeignKey(help_text='The Package that this file is from', on_delete=django.db.models.deletion.CASCADE, to='matchcode.IndexablePackage'), + ), + migrations.AddField( + model_name='approximatedirectorystructureindex', + name='package', + field=models.ForeignKey(help_text='The Package that this directory is a part of', on_delete=django.db.models.deletion.CASCADE, to='matchcode.IndexablePackage'), + ), + migrations.AddField( + model_name='approximatedirectorycontentindex', + name='package', + field=models.ForeignKey(help_text='The Package that this directory is a part of', on_delete=django.db.models.deletion.CASCADE, to='matchcode.IndexablePackage'), + ), + migrations.AlterUniqueTogether( + name='approximatedirectorystructureindex', + unique_together=set([('chunk1', 'chunk2', 'chunk3', 'chunk4', 'package', 'path')]), + ), + migrations.AlterUniqueTogether( + name='approximatedirectorycontentindex', + unique_together=set([('chunk1', 'chunk2', 'chunk3', 'chunk4', 'package', 'path')]), + ), + ] diff --git a/matchcode/src/matchcode/migrations/__init__.py b/matchcode/src/matchcode/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/matchcode/src/matchcode/models.py b/matchcode/src/matchcode/models.py new file mode 100644 index 00000000..690a85da --- /dev/null +++ b/matchcode/src/matchcode/models.py @@ -0,0 +1,376 @@ +# +# Copyright (c) 2020 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# + +from collections import defaultdict +from datetime import datetime +import binascii +import logging +import sys + +from django.core.exceptions import ObjectDoesNotExist +from django.db import models +from django.forms.models import model_to_dict +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ + +from matchcode.fingerprinting import create_halohash_chunks +from matchcode.fingerprinting import split_fingerprint +from matchcode.halohash import byte_hamming_distance +from matchcode.utils import get_error_message +from matchcode.utils import hexstring_to_binarray + +from packagedb.models import Package +from packagedb.models import Resource + + +TRACE = False + +if TRACE: + level = logging.DEBUG +else: + level = logging.ERROR + +logger = logging.getLogger(__name__) +logging.basicConfig(stream=sys.stdout) +logger.setLevel(level) + + +def logger_debug(*args): + return logger.debug(' '.join(isinstance(a, str) and a or repr(a) for a in args)) + + +class IndexablePackage(models.Model): + """ + This is a model that mirrors the existing packages on a PackageDB instance. + """ + uuid = models.UUIDField( + _('UUID'), + unique=True, + help_text='The UUID of a Package on an instance of PackageDB', + db_index=True, + ) + + last_indexed_date = models.DateTimeField( + help_text='Timestamp set to the date of the last indexing. Used to track indexing status.' + ) + + index_error = models.TextField( + null=True, + blank=True, + help_text='Indexing errors messages. When present this means the indexing has failed.', + ) + + @property + def package(self): + # TODO: have option to get data from API vs DB query + return Package.objects.get(uuid=self.uuid) + + @property + def resources(self): + # TODO: Return resources as Codebase + # TODO: have option to get data from API vs DB query + return Resource.objects.filter(package=self.package) + + +def get_or_create_indexable_package(package): + created = False + try: + # Check to see if we have an IndexablePackage for this package + indexable_package = IndexablePackage.objects.get(uuid=package.uuid) + except ObjectDoesNotExist: + indexable_package = IndexablePackage.objects.create( + uuid=package.uuid, + last_indexed_date = timezone.now() + ) + created = True + return indexable_package, created + + +############################################################################### +# FILE MATCHING +############################################################################### +class BaseFileIndex(models.Model): + sha1 = models.BinaryField( + max_length=20, + db_index=True, + help_text='Binary form of a SHA1 checksum in lowercase hex for a file', + null=False, + blank=False, + ) + + package = models.ForeignKey( + IndexablePackage, + help_text='The Package that this file is from', + null=False, + on_delete=models.CASCADE, + ) + + class Meta: + abstract = True + + @classmethod + def index(cls, sha1, indexable_package): + try: + package_data = indexable_package.package + sha1_bin = hexstring_to_binarray(sha1) + bfi, created = cls.objects.get_or_create( + package=indexable_package, + sha1=sha1_bin + ) + if created: + logger.info( + '{} - Inserted {} for Package {}:\t{}'.format( + datetime.utcnow().isoformat(), + bfi.__class__.__name__, + package_data.download_url, + sha1 + ) + ) + return bfi, created + except Exception as e: + msg = f'Error creating {bfi.__class__.__name__}:\n' + msg += get_error_message(e) + indexable_package.index_error = msg + indexable_package.save() + logger.error(msg) + + @classmethod + def match(cls, sha1): + """ + Return a list of matched Packages that contains a file with a SHA1 value of `sha1` + """ + if TRACE: + logger_debug(cls.__name__, 'match:', 'sha1:', sha1) + + if not sha1: + return cls.objects.none() + + sha1_in_bin = hexstring_to_binarray(sha1) + matches = cls.objects.filter(sha1=sha1_in_bin) + if TRACE: + for match in matches: + indexable_package = match.package + dct = model_to_dict(indexable_package.package) + logger_debug(cls.__name__, 'match:', 'matched_file:', dct) + return matches + + def fingerprint(self): + return binascii.hexlify(self.sha1).decode('utf-8') + + +class ExactPackageArchiveIndex(BaseFileIndex): + pass + + +class ExactFileIndex(BaseFileIndex): + pass + + +################################################################################ +# DIRECTORY MATCHING +################################################################################ +def bah128_ranges(indexed_elements_count, range_ratio=0.05): + """ + Return a tuple of two integers, one smaller than `indexed_elements_count` by + `range_ratio` and one larger than `indexed_elements_count` by `range_ratio` + + This helps us match on directories with similar amounts of files. Directory + fingerprints become uncomparable if one fingerprint has more elements + indexed in it than another. + """ + return ( + int(indexed_elements_count * (1 - range_ratio)), + int(indexed_elements_count * (1 + range_ratio)) + ) + + +class BaseDirectoryIndex(models.Model): + indexed_elements_count = models.IntegerField( + help_text='Number of elements that went into the fingerprint', + ) + + chunk1 = models.BinaryField( + max_length=4, + db_index=True, + help_text='Binary form of the first 8 (0-7) hex digits of the fingerprint', + null=False, + blank=False + ) + + chunk2 = models.BinaryField( + max_length=4, + db_index=True, + help_text='Binary form of the second 8 (8-15) hex digits of the fingerprint', + null=False, + blank=False + ) + + chunk3 = models.BinaryField( + max_length=4, + db_index=True, + help_text='Binary form of the third 8 (16-23) hex digits of the fingerprint', + null=False, + blank=False + ) + + chunk4 = models.BinaryField( + max_length=4, + db_index=True, + help_text='Binary form of the fourth 8 (24-32) hex digits of the fingerprint', + null=False, + blank=False + ) + + package = models.ForeignKey( + IndexablePackage, + help_text='The Package that this directory is a part of', + null=False, + on_delete=models.CASCADE, + ) + + path = models.CharField( + max_length=2000, + help_text=_('The full path value of this directory'), + ) + + class Meta: + abstract = True + unique_together = ['chunk1', 'chunk2', 'chunk3', 'chunk4', 'package', 'path'] + + def __str__(self): + return self.fingerprint() + + @classmethod + def index(cls, directory_fingerprint, resource_path, indexable_package): + """ + Index the string `directory_fingerprint` into the BaseDirectoryIndex model + """ + try: + indexed_elements_count, fp = split_fingerprint(directory_fingerprint) + fp_chunk1, fp_chunk2, fp_chunk3, fp_chunk4 = create_halohash_chunks(fp) + bdi, created = cls.objects.get_or_create( + indexed_elements_count=indexed_elements_count, + chunk1=fp_chunk1, + chunk2=fp_chunk2, + chunk3=fp_chunk3, + chunk4=fp_chunk4, + path=resource_path, + package=indexable_package, + ) + if created: + logger.info( + '{} - Inserted {} for Package {}:\t{}'.format( + datetime.utcnow().isoformat(), + bdi.__class__.__name__, + indexable_package.package.download_url, + directory_fingerprint + ) + ) + return bdi, created + except Exception as e: + msg = f'Error creating {bdi.__class__.__name__}:\n' + msg += get_error_message(e) + indexable_package.index_error = msg + indexable_package.save() + logger.error(msg) + + @classmethod + def match(cls, directory_fingerprint): + """ + Return a list of matched Packages + """ + if TRACE: + logger_debug(cls.__name__, 'match:', 'directory_fingerprint:', directory_fingerprint) + + if not directory_fingerprint: + return cls.objects.none() + + # Step 1: find fingerprints with matching chunks + indexed_elements_count, bah128 = split_fingerprint(directory_fingerprint) + chunk1, chunk2, chunk3, chunk4 = create_halohash_chunks(bah128) + range = bah128_ranges(indexed_elements_count) + matches = cls.objects.filter( + models.Q( + indexed_elements_count__range=range, + chunk1=chunk1 + ) | + models.Q( + indexed_elements_count__range=range, + chunk2=chunk2 + ) | + models.Q( + indexed_elements_count__range=range, + chunk3=chunk3 + ) | + models.Q( + indexed_elements_count__range=range, + chunk4=chunk4 + ) + ) + + if TRACE: + for match in matches: + dct = model_to_dict(match) + logger_debug(cls.__name__, 'match:', 'matched_indexable_package:', dct) + + # Step 2: calculate Hamming distance of all matches + + # Store all close matches in a dictionary of querysets + matches_by_hamming_distance = defaultdict(cls.objects.none) + for match in matches: + # Get fingerprint from the match + fp = match.fingerprint() + _, match_bah128 = split_fingerprint(fp) + + # Perform Hamming distance calculation between the fingerprint we + # are looking up and a potential match fingerprint + hd = byte_hamming_distance(bah128, match_bah128) + + # TODO: try other thresholds if this is too restrictive + if hd < 8: + # Save match to `matches_by_hamming_distance` by adding the matched object to the queryset + matches_by_hamming_distance[hd] |= cls.objects.filter(pk=match.pk) + + if TRACE: + logger_debug(list(matches_by_hamming_distance.items())) + + # Step 3: order matches from lowest Hamming distance to highest Hamming distance + # TODO: consider limiting matches for brevity + good_matches = cls.objects.none() + for hamming_distance, match in sorted(matches_by_hamming_distance.items()): + if hamming_distance == 0: + # If we have an exact match, return and disregard others + good_matches |= match + break + else: + # If we don't have an exact match, add all close matches we have + good_matches |= match + + if TRACE: + for match in good_matches: + dct = model_to_dict(match) + logger_debug(cls.__name__, 'match:', 'good_matched_indexable_package:', dct) + + return good_matches + + def get_chunks(self): + chunk1 = binascii.hexlify(self.chunk1) + chunk2 = binascii.hexlify(self.chunk2) + chunk3 = binascii.hexlify(self.chunk3) + chunk4 = binascii.hexlify(self.chunk4) + return chunk1, chunk2, chunk3, chunk4 + + def fingerprint(self): + indexed_element_count_as_hex_bytes = b'%08x' % self.indexed_elements_count + chunk1, chunk2, chunk3, chunk4 = self.get_chunks() + fingerprint = indexed_element_count_as_hex_bytes + chunk1 + chunk2 + chunk3 + chunk4 + return fingerprint.decode('utf-8') + + +class ApproximateDirectoryStructureIndex(BaseDirectoryIndex): + pass + + +class ApproximateDirectoryContentIndex(BaseDirectoryIndex): + pass diff --git a/matchcode/src/matchcode/utils.py b/matchcode/src/matchcode/utils.py new file mode 100644 index 00000000..72a41646 --- /dev/null +++ b/matchcode/src/matchcode/utils.py @@ -0,0 +1,182 @@ +# +# Copyright (c) 2020 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# + +from os import getenv +from unittest import TestCase +import binascii +import json +import ntpath +import os +import posixpath +import traceback + +from django.conf import settings +from django.test import TestCase as DjangoTestCase + +from commoncode.resource import VirtualCodebase +from packagedb.models import Package +from packagedb.models import Resource + + +############## TEST UTILITIES ############## +""" +The conventions used for the tests are: +- for tests that require files these are stored in the testfiles directory +- each test must use its own sub directory in testfiles. The is called the +'base' +- testfiles that are more than a few KB should be in a bzip2 tarball +""" + + +class BaseTestCase(TestCase): + BASE_DIR = os.path.join(os.path.dirname(__file__), 'testfiles') + + @classmethod + def get_test_loc(cls, path): + """ + Given a path relative to the test files directory, return the location + to a test file or directory for this path. No copy is done. + """ + path = to_os_native_path(path) + location = os.path.abspath(os.path.join(cls.BASE_DIR, path)) + return location + + +class CodebaseTester(object): + def check_codebase(self, codebase, expected_codebase_json_loc, + regen=False, remove_file_date=True): + """ + Check the Resources of the `codebase` Codebase objects are the same + as the data in the `expected_codebase_json_loc` JSON file location, + + If `regen` is True the expected_file WILL BE overwritten with the `codebase` + data. This is convenient for updating tests expectations. But use with + caution. + + if `remove_file_date` is True, the file.date attribute is removed. + """ + + def serializer(r): + rd = r.to_dict(with_info=True) + if remove_file_date: + rd.pop('file_data', None) + return rd + + results = list(map(serializer, codebase.walk(topdown=True))) + if regen: + with open(expected_codebase_json_loc, 'w') as reg: + json.dump(dict(files=results), reg, indent=2, separators=(',', ': ')) + + expected_vc = VirtualCodebase(location=expected_codebase_json_loc) + expected = list(map(serializer, expected_vc.walk(topdown=True))) + + # NOTE we redump the JSON as a string for a more efficient display of the + # failures comparison/diff + expected = json.dumps(expected, indent=2, separators=(',', ': ')) + results = json.dumps(results, indent=2, separators=(',', ': ')) + self.assertEqual(expected, results) + + +class MatchcodeTestCase(CodebaseTester, BaseTestCase, DjangoTestCase): + databases = '__all__' + + +def to_os_native_path(path): + """ + Normalize a path to use the native OS path separator. + """ + path = path.replace(posixpath.sep, os.path.sep) + path = path.replace(ntpath.sep, os.path.sep) + path = path.rstrip(os.path.sep) + return path + + +def load_resources_from_scan(scan_location, package): + vc = VirtualCodebase( + location=scan_location, + ) + for resource in vc.walk(topdown=True): + created_resource, _ = Resource.objects.get_or_create( + package=package, + path=resource.path, + size=resource.size, + sha1=resource.sha1, + md5=resource.md5, + is_file=resource.type == 'file' + ) + + +def index_packages_sha1(): + """ + Reindex all the packages for exact sha1 matching. + """ + from matchcode.models import ExactPackageArchiveIndex + from matchcode.models import get_or_create_indexable_package + + for package in Package.objects.filter(sha1__isnull=False): + indexable_package, _ = get_or_create_indexable_package(package) + sha1_in_bin = hexstring_to_binarray(package.sha1) + _ = ExactPackageArchiveIndex.objects.create( + package=indexable_package, + sha1=sha1_in_bin + ) + + +def index_package_files_sha1(package, scan_location): + """ + Index for SHA1 the package files found in the JSON scan at scan_location + """ + from matchcode.models import ExactFileIndex + from matchcode.models import get_or_create_indexable_package + + indexable_package, _ = get_or_create_indexable_package(package) + resource_attributes = dict() + vc = VirtualCodebase( + location=scan_location, + resource_attributes=resource_attributes + ) + + for resource in vc.walk(topdown=True): + sha1 = resource.sha1 + if not sha1: + continue + sha1_in_bin = hexstring_to_binarray(sha1) + package_file, created = ExactFileIndex.objects.get_or_create( + sha1=sha1_in_bin, + package=indexable_package, + ) + +################# GENERAL UTILITIES ################# +def hexstring_to_binarray(hex_string): + """ + Convert a hex string to binary form, then store in a bytearray + """ + return bytearray(binascii.unhexlify(hex_string)) + + +def get_error_message(e): + """ + Return an error message with a traceback given an exception. + """ + tb = traceback.format_exc() + msg = e.__class__.__name__ + ' ' + repr(e) + msg += '\n' + tb + return msg + + +def get_settings(var_name): + """ + Return the settings value from the environment or Django settings. + """ + return getenv(var_name) or getattr(settings, var_name, None) or '' + + +def path_suffixes(path): + """ + Yield all the suffixes of `path`, starting from the longest (e.g. more segments). + """ + segments = path.strip('/').split('/') + suffixes = (segments[i:] for i in range(len(segments))) + for suffix in suffixes: + yield '/'.join(suffix) diff --git a/matchcode/src/matchcodeio/__init__.py b/matchcode/src/matchcodeio/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/matchcode/src/matchcodeio/settings/__init__.py b/matchcode/src/matchcodeio/settings/__init__.py new file mode 100644 index 00000000..01eb05db --- /dev/null +++ b/matchcode/src/matchcodeio/settings/__init__.py @@ -0,0 +1,277 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +import sys +from pathlib import Path + +import environ + + +PROJECT_DIR = Path(__file__).resolve().parent.parent +ROOT_DIR = PROJECT_DIR.parent.parent + + +# Environment + +ENV_FILE = "/etc/matchcodeio/.env" +if not Path(ENV_FILE).exists(): + ENV_FILE = ROOT_DIR / ".env" + +env = environ.Env() +environ.Env.read_env(str(ENV_FILE)) + +# Security + +SECRET_KEY = env.str("SECRET_KEY") + +ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=[".localhost", "127.0.0.1", "[::1]"]) + +# SECURITY WARNING: do not run with debug turned on in production +DEBUG = env.bool("MATCHCODEIO_DEBUG", default=False) + +MATCHCODEIO_REQUIRE_AUTHENTICATION = env.bool( + "MATCHCODEIO_REQUIRE_AUTHENTICATION", default=False +) + +# SECURITY WARNING: do not run with debug turned on in production +DEBUG_TOOLBAR = env.bool("MATCHCODEIO_DEBUG_TOOLBAR", default=False) + +MATCHCODEIO_PASSWORD_MIN_LENGTH = env.int("MATCHCODEIO_PASSWORD_MIN_LENGTH", default=14) + +# Matchcode.io + +MATCHCODEIO_LOG_LEVEL = env.str("MATCHCODEIO_LOG_LEVEL", "INFO") + +# Application definition + +INSTALLED_APPS = ( + # Local apps + # Must come before Third-party apps for proper templates override + 'matchcode', + 'packagedb', + # Django built-in + "django.contrib.auth", + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'django.contrib.admin', + "django.contrib.humanize", + # Third-party apps + 'django_filters', + 'rest_framework', +) + +MIDDLEWARE = ( + "django.middleware.security.SecurityMiddleware", + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +) + +ROOT_URLCONF = 'matchcodeio.urls' + +WSGI_APPLICATION = "matchcodeio.wsgi.application" + +# Database + +DATABASE_ROUTERS = ['matchcodeio.settings.dbrouter.PackagedbRouter'] +DATABASE_APPS_MAPPING = {'packagedb': 'packagedb'} +DATABASES = { + 'default': { + 'ENGINE': env.str('MATCHCODEIO_DB_ENGINE', 'django.db.backends.postgresql'), + 'HOST': env.str('MATCHCODEIO_DB_HOST', 'localhost'), + 'NAME': env.str('MATCHCODEIO_DB_NAME', 'matchcode'), + 'USER': env.str('MATCHCODEIO_DB_USER', 'matchcode'), + 'PASSWORD': env.str('MATCHCODEIO_DB_PASSWORD', 'matc34-u5er'), + 'PORT': env.str('MATCHCODEIO_DB_PORT', '5432'), + 'ATOMIC_REQUESTS': True, + }, + 'packagedb': { + "ENGINE": env.str("PACKAGEDB_DB_ENGINE", "django.db.backends.postgresql"), + "HOST": env.str("PACKAGEDB_DB_HOST", "localhost"), + "NAME": env.str("PACKAGEDB_DB_NAME", "packagedb"), + "USER": env.str("PACKAGEDB_DB_USER", "packagedb"), + "PASSWORD": env.str("PACKAGEDB_DB_PASSWORD", "packagedb"), + "PORT": env.str("PACKAGEDB_DB_PORT", "5432"), + "ATOMIC_REQUESTS": True, + } +} + +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" + +# Templates + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + "DIRS": [str(PROJECT_DIR.joinpath("templates"))], + "APP_DIRS": True, + 'OPTIONS': { + "debug": DEBUG, + 'context_processors': [ + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'django.template.context_processors.request', + "django.template.context_processors.static", + ], + }, + }, +] + +# Login + +LOGIN_REDIRECT_URL = "/" +LOGOUT_REDIRECT_URL = "/" + +# Passwords + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + "OPTIONS": { + "min_length": MATCHCODEIO_PASSWORD_MIN_LENGTH, + }, + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + +# Testing + +# True if running tests through `./manage test or pytest` +IS_TESTS = any(clue in sys.argv for clue in ("test", "pytest")) + +# Cache + +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + "LOCATION": "default", + } +} + +# Logging + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "simple": { + "format": "{levelname} {message}", + "style": "{", + }, + }, + "handlers": { + "null": { + "class": "logging.NullHandler", + }, + "console": { + "class": "logging.StreamHandler", + "formatter": "simple", + }, + }, + "loggers": { + "scanpipe": { + "handlers": ["null"] if IS_TESTS else ["console"], + "level": MATCHCODEIO_LOG_LEVEL, + "propagate": False, + }, + "django": { + "handlers": ["null"] if IS_TESTS else ["console"], + "propagate": False, + }, + # Set MATCHCODEIO_LOG_LEVEL=DEBUG to display all SQL queries in the console. + "django.db.backends": { + "level": MATCHCODEIO_LOG_LEVEL, + }, + }, +} + +# Internationalization + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = env.str("TIME_ZONE", default="UTC") + +USE_I18N = True + +USE_TZ = True + +# Static files (CSS, JavaScript, Images) + +STATIC_URL = '/static/' + +STATIC_ROOT = '/var/matchcodeio/static/' + +STATICFILES_DIRS = [ + PROJECT_DIR / 'static', +] + +# Third-party apps + +# Django restframework + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': (), + "DEFAULT_PERMISSION_CLASSES": (), + 'DEFAULT_RENDERER_CLASSES': ( + 'rest_framework.renderers.JSONRenderer', + 'rest_framework.renderers.BrowsableAPIRenderer', + 'rest_framework.renderers.AdminRenderer', + ), + 'DEFAULT_FILTER_BACKENDS': ( + 'django_filters.rest_framework.DjangoFilterBackend', + 'rest_framework.filters.SearchFilter', + ), + 'DEFAULT_PAGINATION_CLASS': 'matchcode.api_custom.PageSizePagination', + # Limit the load on the Database returning a small number of records by default. https://github.com/nexB/vulnerablecode/issues/819 + "PAGE_SIZE": 10, +} + +if not MATCHCODEIO_REQUIRE_AUTHENTICATION: + REST_FRAMEWORK["DEFAULT_PERMISSION_CLASSES"] = ( + "rest_framework.permissions.AllowAny", + ) + +if DEBUG_TOOLBAR: + INSTALLED_APPS += ("debug_toolbar",) + + MIDDLEWARE += ("debug_toolbar.middleware.DebugToolbarMiddleware",) + + DEBUG_TOOLBAR_PANELS = ( + "debug_toolbar.panels.history.HistoryPanel", + "debug_toolbar.panels.versions.VersionsPanel", + "debug_toolbar.panels.timer.TimerPanel", + "debug_toolbar.panels.settings.SettingsPanel", + "debug_toolbar.panels.headers.HeadersPanel", + "debug_toolbar.panels.request.RequestPanel", + "debug_toolbar.panels.sql.SQLPanel", + "debug_toolbar.panels.staticfiles.StaticFilesPanel", + "debug_toolbar.panels.templates.TemplatesPanel", + "debug_toolbar.panels.cache.CachePanel", + "debug_toolbar.panels.signals.SignalsPanel", + "debug_toolbar.panels.logging.LoggingPanel", + "debug_toolbar.panels.redirects.RedirectsPanel", + "debug_toolbar.panels.profiling.ProfilingPanel", + ) + + INTERNAL_IPS = [ + "127.0.0.1", + ] diff --git a/matchcode/src/matchcodeio/settings/dbrouter.py b/matchcode/src/matchcodeio/settings/dbrouter.py new file mode 100644 index 00000000..59a8a0da --- /dev/null +++ b/matchcode/src/matchcodeio/settings/dbrouter.py @@ -0,0 +1,21 @@ +class PackagedbRouter(object): + def db_for_read(self, model, **hints): + if model._meta.app_label == 'packagedb': + return 'packagedb' + return None + + def db_for_write(self, model, **hints): + if model._meta.app_label == 'packagedb': + return 'packagedb' + return None + + def allow_relation(self, obj1, obj2, **hints): + if obj1._meta.app_label == 'packagedb' or \ + obj2._meta.app_label == 'packagedb': + return True + return None + + def allow_migrate(self, db, app_label, model_name=None, **hints): + if app_label == 'packagedb': + return db == 'packagedb' + return None diff --git a/matchcode/src/matchcodeio/urls.py b/matchcode/src/matchcodeio/urls.py new file mode 100644 index 00000000..d4c04c82 --- /dev/null +++ b/matchcode/src/matchcodeio/urls.py @@ -0,0 +1,28 @@ +# +# Copyright (c) 2016 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# + +from django.conf.urls import include +from django.urls import re_path + +from packagedb.api import PackageViewSet +from packagedb.api import ResourceViewSet +from rest_framework import routers + +from matchcode.api import ApproximateDirectoryContentIndexViewSet +from matchcode.api import ApproximateDirectoryStructureIndexViewSet +from matchcode.api import ExactFileIndexViewSet +from matchcode.api import ExactPackageArchiveIndexViewSet + + +api_router = routers.DefaultRouter() +api_router.register(r'packages', PackageViewSet) +api_router.register(r'resources', ResourceViewSet) +api_router.register(r'approximate_directory_content_index', ApproximateDirectoryContentIndexViewSet) +api_router.register(r'approximate_directory_structure_index', ApproximateDirectoryStructureIndexViewSet) +api_router.register(r'exact_file_index', ExactFileIndexViewSet) +api_router.register(r'exact_package_archive_index', ExactPackageArchiveIndexViewSet) + +urlpatterns = [ + re_path(r'^api/', include((api_router.urls, 'api'))), +] diff --git a/matchcode/src/matchcodeio/wsgi.py b/matchcode/src/matchcodeio/wsgi.py new file mode 100644 index 00000000..ade3f636 --- /dev/null +++ b/matchcode/src/matchcodeio/wsgi.py @@ -0,0 +1,18 @@ +# +# Copyright (c) nexB, Inc. http://www.nexb.com/ - All rights reserved. +# + +import os +from django.core.wsgi import get_wsgi_application + + +""" +WSGI config for matchcodeio project. + +It exposes the WSGI callable as a module-level variable named ``application``. +""" + + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'matchcodeio.settings') + +application = get_wsgi_application() diff --git a/matchcode/tests/__init__.py b/matchcode/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/matchcode/tests/matchcode/__init__.py b/matchcode/tests/matchcode/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/matchcode/tests/matchcode/test_api.py b/matchcode/tests/matchcode/test_api.py new file mode 100644 index 00000000..7eeb3e64 --- /dev/null +++ b/matchcode/tests/matchcode/test_api.py @@ -0,0 +1,159 @@ +# +# Copyright (c) nexB Inc. http://www.nexb.com/ - All rights reserved. +# + +import os + +from django.urls import reverse + +from packagedb.models import Package + +from matchcode.indexing import index_package_directories +from matchcode.models import get_or_create_indexable_package +from matchcode.utils import load_resources_from_scan +from matchcode.utils import MatchcodeTestCase + + +class ApproximateDirectoryStructureIndexAPITestCase(MatchcodeTestCase): + BASE_DIR = os.path.join(os.path.dirname(__file__), 'testfiles') + + def setUp(self): + # Execute the superclass' setUp method before creating our own + # DB objects + super().setUp() + + self.test_package1, _ = Package.objects.get_or_create( + filename='plugin-request-2.4.1.tgz', + sha1='7295749caddd3c52be472eef6623a7b441ed17d6', + size=7269, + name='plugin-request', + version='2.4.1', + download_url='https://registry.npmjs.org/@umijs/plugin-request/-/plugin-request-2.4.1.tgz', + type='npm', + ) + load_resources_from_scan(self.get_test_loc('match/nested/plugin-request-2.4.1-ip.json'), self.test_package1) + self.test_indexable_package1, _ = get_or_create_indexable_package(self.test_package1) + index_package_directories(self.test_indexable_package1) + + self.test_package2, _ = Package.objects.get_or_create( + filename='underscore-1.10.9.tgz', + sha1='ba7a9cfc15873e67821611503a34a7c26bf7264f', + size=26569, + name='underscore', + version='1.10.9', + download_url='https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz', + type='npm', + ) + load_resources_from_scan(self.get_test_loc('match/nested/underscore-1.10.9-ip.json'), self.test_package2) + self.test_indexable_package2, _ = get_or_create_indexable_package(self.test_package2) + index_package_directories(self.test_indexable_package2) + + def test_api_approximate_directory_content_index_list_fingerprint_lookup(self): + test_fingerprint = '00000007af7d63765c78fa516b5353f5ffa7df45' + response = self.client.get( + reverse('api:approximatedirectorycontentindex-list'), + data={'fingerprint': test_fingerprint} + ) + self.assertEqual(200, response.status_code) + results = response.data.get('results', []) + self.assertEqual(1, len(results)) + result = results[0] + expected_result = { + 'fingerprint': '00000007af7d63765c78fa516b5353f5ffa7df45', + 'purl': 'pkg:npm/plugin-request@2.4.1' + } + self.assertEqual(expected_result, result) + + def test_api_approximate_directory_structure_index_list_fingerprint_lookup(self): + test_fingerprint = '00000004d10982208810240820080a6a3e852486' + response = self.client.get( + reverse('api:approximatedirectorystructureindex-list'), + data={'fingerprint': test_fingerprint} + ) + self.assertEqual(200, response.status_code) + results = response.data.get('results', []) + self.assertEqual(1, len(results)) + result = results[0] + expected_result = { + 'fingerprint': '00000004d10982208810240820080a6a3e852486', + 'purl': 'pkg:npm/underscore@1.10.9' + } + self.assertEqual(expected_result, result) + + def test_api_approximate_directory_content_index_match_no_match(self): + test_fingerprint = '000000020e1d2124040134564e1941a6a620db34' + response = self.client.get( + reverse('api:approximatedirectorycontentindex-match'), + data={'fingerprint': test_fingerprint} + ) + results = response.data + self.assertEqual(0, len(results)) + + def test_api_approximate_directory_structure_index_match_no_match(self): + test_fingerprint = '00000004d10982789010240876580a6a3e852485' + response = self.client.get( + reverse('api:approximatedirectorystructureindex-match'), + data={'fingerprint': test_fingerprint} + ) + results = response.data + self.assertEqual(0, len(results)) + + def test_api_approximate_directory_content_index_match_close_match(self): + # This test fingerprint has a hamming distance of 7 from the expected fingerprint + test_fingerprint = '00000007af7d63765c78fa516b5353f5ffa7d000' + response = self.client.get( + reverse('api:approximatedirectorycontentindex-match'), + data={'fingerprint': test_fingerprint} + ) + results = response.data + self.assertEqual(1, len(results)) + result = results[0] + self.assertEqual(test_fingerprint, result['fingerprint']) + expected_matched_fingerprint = '00000007af7d63765c78fa516b5353f5ffa7df45' + self.assertEqual(expected_matched_fingerprint, result['matched_fingerprint']) + expected_purl = 'pkg:npm/plugin-request@2.4.1' + self.assertEqual(expected_purl, result['purl']) + + def test_api_approximate_directory_structure_index_match_close_match(self): + # This test fingerprint has a hamming distance of 7 from the expected fingerprint + test_fingerprint = '00000004d10982208810240820080a6a3e800000' + response = self.client.get( + reverse('api:approximatedirectorystructureindex-match'), + data={'fingerprint': test_fingerprint} + ) + results = response.data + self.assertEqual(1, len(results)) + result = results[0] + self.assertEqual(test_fingerprint, result['fingerprint']) + expected_matched_fingerprint = '00000004d10982208810240820080a6a3e852486' + self.assertEqual(expected_matched_fingerprint, result['matched_fingerprint']) + expected_purl = 'pkg:npm/underscore@1.10.9' + self.assertEqual(expected_purl, result['purl']) + + def test_api_approximate_directory_content_index_match(self): + test_fingerprint = '00000007af7d63765c78fa516b5353f5ffa7df45' + response = self.client.get( + reverse('api:approximatedirectorycontentindex-match'), + data={'fingerprint': test_fingerprint} + ) + results = response.data + self.assertEqual(1, len(results)) + result = results[0] + self.assertEqual(test_fingerprint, result['fingerprint']) + self.assertEqual(test_fingerprint, result['matched_fingerprint']) + expected_purl = 'pkg:npm/plugin-request@2.4.1' + self.assertEqual(expected_purl, result['purl']) + + def test_api_approximate_directory_structure_index_match(self): + test_fingerprint = '00000004d10982208810240820080a6a3e852486' + response = self.client.get( + reverse('api:approximatedirectorystructureindex-match'), + data={'fingerprint': test_fingerprint} + ) + results = response.data + self.assertEqual(1, len(results)) + result = results[0] + self.assertEqual(test_fingerprint, result['fingerprint']) + self.assertEqual(test_fingerprint, result['matched_fingerprint']) + expected_purl = 'pkg:npm/underscore@1.10.9' + self.assertEqual(expected_purl, result['purl']) diff --git a/matchcode/tests/matchcode/test_fingerprinting.py b/matchcode/tests/matchcode/test_fingerprinting.py new file mode 100644 index 00000000..12d97d67 --- /dev/null +++ b/matchcode/tests/matchcode/test_fingerprinting.py @@ -0,0 +1,101 @@ +# +# Copyright (c) nexB Inc. +# +import os + +from commoncode.resource import VirtualCodebase +from commoncode.testcase import FileBasedTesting + +from matchcode.fingerprinting import _create_directory_fingerprint +from matchcode.fingerprinting import _get_resource_subpath +from matchcode.fingerprinting import compute_directory_fingerprints +from matchcode.fingerprinting import create_content_fingerprint +from matchcode.fingerprinting import create_halohash_chunks +from matchcode.fingerprinting import create_structure_fingerprint +from matchcode.fingerprinting import split_fingerprint + + +class Resource(): + def __init__(self, path='', size=0, sha1=''): + self.path = path + self.size = size + self.sha1 = sha1 + + +class TestFingerprintingFunctions(FileBasedTesting): + test_data_dir = os.path.join(os.path.dirname(__file__), 'testfiles/fingerprinting') + + def test__create_directory_fingerprint(self): + test_input = [ + 'package', + 'package/readme.txt', + 'package/index.js', + 'package/package.json', + ] + directory_fingerprint = _create_directory_fingerprint(test_input) + expected_directory_fingerprint = '0000000410d24471969646cb5402032288493126' + self.assertEqual(expected_directory_fingerprint, directory_fingerprint) + indexed_elements_count, _ = split_fingerprint(directory_fingerprint) + self.assertEqual(len(test_input), indexed_elements_count) + + def test_split_fingerprint(self): + directory_fingerprint = '0000000410d24471969646cb5402032288493126' + indexed_elements_count, bah128 = split_fingerprint(directory_fingerprint) + + expected_indexed_elements_count = 4 + self.assertEqual(expected_indexed_elements_count, indexed_elements_count) + + expected_bah128 = '10d24471969646cb5402032288493126' + self.assertEqual(expected_bah128, bah128) + + def test_create_content_fingerprint(self): + test_resources = [ + Resource(sha1='d4e4abbe8e2a8169d6a52907152c2c80ec884745'), + Resource(sha1='0c94f137f6e0536db8cb2622a9dc84253b91b90c'), + Resource(sha1='10cab45fe6f353b47b587a576c1077a96ce348f5'), + Resource(sha1='134f2b052b6e5f56b631be2eded70f89d44cf381'), + ] + fingerprint = create_content_fingerprint(test_resources) + expected_fingerprint = '00000004005b88c2800f0044044781ae05680419' + self.assertEqual(expected_fingerprint, fingerprint) + + def test__get_resource_subpath(self): + test_resource = Resource(path='foo/bar/baz/qux.c') + test_top_resource = Resource(path='foo/bar/') + subpath = _get_resource_subpath(test_resource, test_top_resource) + expected_subpath = 'baz/qux.c' + self.assertEqual(expected_subpath, subpath) + + def test_create_structure_fingerprint(self): + test_top_resource = Resource(path='package') + test_child_resources = [ + Resource(path='package/readme.txt', size=771), + Resource(path='package/index.js', size=608), + Resource(path='package/package.json', size=677), + ] + fingerprint = create_structure_fingerprint(test_top_resource, test_child_resources) + expected_fingerprint = '00000003ce72f4308a1bc1afb0fb47ed590b5c53' + self.assertEqual(expected_fingerprint, fingerprint) + + def test_create_halohash_chunks(self): + test_bah128 = 'ce72f4308a1bc1afb0fb47ed590b5c53' + chunk1, chunk2, chunk3, chunk4 = create_halohash_chunks(test_bah128) + expected_chunk1 = bytearray(b'\xcer\xf40') + expected_chunk2 = bytearray(b'\x8a\x1b\xc1\xaf') + expected_chunk3 = bytearray(b'\xb0\xfbG\xed') + expected_chunk4 = bytearray(b'Y\x0b\\S') + self.assertEqual(chunk1, expected_chunk1) + self.assertEqual(chunk2, expected_chunk2) + self.assertEqual(chunk3, expected_chunk3) + self.assertEqual(chunk4, expected_chunk4) + + def test_compute_directory_fingerprints(self): + scan_loc = self.get_test_loc('abbrev-1.0.3-i.json') + vc = VirtualCodebase(location=scan_loc) + vc = compute_directory_fingerprints(vc) + directory_content = vc.root.extra_data['directory_content'] + directory_structure = vc.root.extra_data['directory_structure'] + expected_directory_content = '0000000346ce04751a3c98f00086f16a91d9790b' + expected_directory_structure = '000000034f9bf110673bdf06197cd514a799a66c' + self.assertEqual(expected_directory_content, directory_content) + self.assertEqual(expected_directory_structure, directory_structure) diff --git a/matchcode/tests/matchcode/test_index_packages.py b/matchcode/tests/matchcode/test_index_packages.py new file mode 100644 index 00000000..1a777a43 --- /dev/null +++ b/matchcode/tests/matchcode/test_index_packages.py @@ -0,0 +1,221 @@ +# +# Copyright (c) 2020 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# + +import os + +from commoncode.resource import VirtualCodebase + +from matchcode.fingerprinting import compute_directory_fingerprints +from matchcode.indexing import _create_virtual_codebase_from_indexable_package +from matchcode.indexing import index_directory_fingerprints +from matchcode.indexing import index_package_archives +from matchcode.indexing import index_package_directories +from matchcode.indexing import index_package_file +from matchcode.management.commands import index_packages +from matchcode.models import ApproximateDirectoryContentIndex +from matchcode.models import ApproximateDirectoryStructureIndex +from matchcode.models import create_halohash_chunks +from matchcode.models import hexstring_to_binarray +from matchcode.models import IndexablePackage +from matchcode.models import ExactPackageArchiveIndex +from matchcode.models import ExactFileIndex +from matchcode.models import get_or_create_indexable_package +from matchcode.utils import load_resources_from_scan +from matchcode.utils import MatchcodeTestCase +from packagedb.models import Package +from packagedb.models import Resource + + +class IndexPackagesTestCase(MatchcodeTestCase): + BASE_DIR = os.path.join(os.path.dirname(__file__), 'testfiles') + maxDiff = None + + def setUp(self): + # Ensure database is empty before adding test packages + Package.objects.all().delete() + + # Single object, single source + self.test_package1, _ = Package.objects.get_or_create( + filename='abbot-0.12.3.jar', + sha1='51d28a27d919ce8690a40f4f335b9d591ceb16e9', + md5='38206e62a54b0489fb6baa4db5a06093', + size=689791, + name='abbot', + version='0.12.3', + download_url='http://repo1.maven.org/maven2/abbot/abbot/0.12.3/abbot-0.12.3.jar', + type='maven', + ) + self.scan1 = self.get_test_loc('match/scan1.json') + load_resources_from_scan(self.scan1, self.test_package1) + + def test_index_packages(self): + # Ensure ApproximateDirectoryStructureIndex, IndexablePackage, + # ExactPackageArchiveIndex, and ExactFileIndex tables are empty + self.assertFalse(ApproximateDirectoryStructureIndex.objects.all()) + self.assertFalse(IndexablePackage.objects.all()) + self.assertFalse(ExactPackageArchiveIndex.objects.all()) + self.assertFalse(ExactFileIndex.objects.all()) + + # Populate fingerprint tables from Package and Resources + package_indexer = index_packages.Command() + package_indexer.handle() + + # See if the tables have been populated properly + indexable_packages = IndexablePackage.objects.all() + self.assertEqual(1, len(indexable_packages)) + indexable_package = indexable_packages[0] + self.assertEqual(self.test_package1.uuid, indexable_package.uuid) + self.assertTrue(indexable_package.last_indexed_date) + self.assertFalse(indexable_package.index_error) + + package_archive_sha1s = ExactPackageArchiveIndex.objects.all() + self.assertEqual(1, len(package_archive_sha1s)) + package_archive_sha1 = package_archive_sha1s[0] + expected_sha1 = self.test_package1.sha1 + self.assertEqual(expected_sha1, package_archive_sha1.fingerprint()) + self.assertEqual(indexable_package, package_archive_sha1.package) + + vc = VirtualCodebase(location=self.scan1) + expected_resources = [r for r in vc.walk(topdown=True) if r.type == 'file'] + package_file_sha1s = ExactFileIndex.objects.all() + self.assertEqual(len(expected_resources), len(package_file_sha1s)) + for expected_resource, package_file_sha1 in zip(expected_resources, package_file_sha1s): + self.assertEqual(expected_resource.sha1, package_file_sha1.fingerprint()) + self.assertEqual(indexable_package, package_file_sha1.package) + + directory_structure_fingerprints = ApproximateDirectoryStructureIndex.objects.filter(package=indexable_package).order_by('path') + # Only one directory should be indexed since we do not create directory + # fingerprints for directories with only one file in them + self.assertEqual(1, len(directory_structure_fingerprints)) + + result_1 = directory_structure_fingerprints[0] + self.assertEqual('test', result_1.path) + self.assertEqual(indexable_package, result_1.package) + r1_chunk1, r1_chunk2, r1_chunk3, r1_chunk4 = create_halohash_chunks('160440008028c38c24a8038040006040') + self.assertEqual(r1_chunk1, result_1.chunk1) + self.assertEqual(r1_chunk2, result_1.chunk2) + self.assertEqual(r1_chunk3, result_1.chunk3) + self.assertEqual(r1_chunk4, result_1.chunk4) + + def test_index_packages_index_directory_structure_fingerprints(self): + indexable_package, _ = get_or_create_indexable_package(self.test_package1) + index_packages.index_package_directories(indexable_package) + directory_structure_fingerprints = ApproximateDirectoryStructureIndex.objects.filter(package=indexable_package).order_by('path') + self.assertEqual(1, len(directory_structure_fingerprints)) + + result_1 = directory_structure_fingerprints[0] + self.assertEqual('test', result_1.path) + self.assertEqual(indexable_package, result_1.package) + + expected_chunk1 = hexstring_to_binarray('16044000') + expected_chunk2 = hexstring_to_binarray('8028c38c') + expected_chunk3 = hexstring_to_binarray('24a80380') + expected_chunk4 = hexstring_to_binarray('40006040') + + self.assertEqual(expected_chunk1, result_1.chunk1) + self.assertEqual(expected_chunk2, result_1.chunk2) + self.assertEqual(expected_chunk3, result_1.chunk3) + self.assertEqual(expected_chunk4, result_1.chunk4) + + def test_index_package_archives(self): + # Ensure ExactPackageArchiveIndex table is empty + self.assertFalse(ExactPackageArchiveIndex.objects.all()) + + # Create indexable_package from test_package + indexable_package, _ = get_or_create_indexable_package(self.test_package1) + + # Load ExactPackageArchiveIndex table + created = index_package_archives(indexable_package) + + # Check to see if new ExactPackageArchiveIndex was created + self.assertTrue(created) + self.assertEqual(1, ExactPackageArchiveIndex.objects.all().count()) + + # Ensure the created ExactPackageArchiveIndex indexes the correct checksum and is related to the right Package + result = ExactPackageArchiveIndex.objects.all()[0] + + self.assertEqual(self.test_package1.sha1, result.fingerprint()) + self.assertEqual(indexable_package, result.package) + + def test_index_package_file(self): + # Create indexable_package from test_package + indexable_package, _ = get_or_create_indexable_package(self.test_package1) + + # Ensure ExactFileIndex is empty prior to test + self.assertFalse(ExactFileIndex.objects.all()) + + # Get one resource from test_package1 and index it + resource = indexable_package.resources.filter(is_file=True)[0] + created_exact_file_index, _ = index_package_file(resource) + + self.assertTrue(created_exact_file_index) + self.assertEqual(1, ExactFileIndex.objects.all().count()) + result = ExactFileIndex.objects.all()[0] + + expected_fingerprint = '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8' + self.assertEqual(expected_fingerprint, result.fingerprint()) + self.assertEqual(indexable_package, result.package) + + def test__create_virtual_codebase_from_indexable_package(self): + # Create indexable_package from test_package + indexable_package, _ = get_or_create_indexable_package(self.test_package1) + + vc = _create_virtual_codebase_from_indexable_package(indexable_package) + expected_vc = VirtualCodebase(location=self.scan1) + + # Ensure that at least the directory structure is the same + for expected_r, r in zip(expected_vc.walk(), vc.walk()): + self.assertEqual(expected_r.path, r.path) + + def test_index_directory_fingerprints(self): + # Create indexable_package from test_package + indexable_package, _ = get_or_create_indexable_package(self.test_package1) + vc = _create_virtual_codebase_from_indexable_package(indexable_package) + vc = compute_directory_fingerprints(vc) + + # Ensure tables are empty prior to indexing + self.assertFalse(ApproximateDirectoryContentIndex.objects.all()) + self.assertFalse(ApproximateDirectoryStructureIndex.objects.all()) + + indexed_adci, indexed_adsi = index_directory_fingerprints(vc, indexable_package) + + # Check to see if anything has been indexed + self.assertEqual(1, indexed_adci) + self.assertEqual(1, indexed_adsi) + self.assertEqual(1, ApproximateDirectoryContentIndex.objects.all().count()) + self.assertEqual(1, ApproximateDirectoryStructureIndex.objects.all().count()) + + # Check to see if the correct values have been indexed + adci = ApproximateDirectoryContentIndex.objects.all()[0] + adsi = ApproximateDirectoryStructureIndex.objects.all()[0] + + expected_adci_fingerprint = '0000000288212131028101000400403044049614' + expected_adsi_fingerprint = '00000002160440008028c38c24a8038040006040' + self.assertEqual(expected_adci_fingerprint, adci.fingerprint()) + self.assertEqual(expected_adsi_fingerprint, adsi.fingerprint()) + + def test_index_package_directories(self): + # Create indexable_package from test_package + indexable_package, _ = get_or_create_indexable_package(self.test_package1) + + # Ensure tables are empty prior to indexing + self.assertFalse(ApproximateDirectoryContentIndex.objects.all()) + self.assertFalse(ApproximateDirectoryStructureIndex.objects.all()) + + indexed_adci, indexed_adsi = index_package_directories(indexable_package) + + # Check to see if anything has been indexed + self.assertEqual(1, indexed_adci) + self.assertEqual(1, indexed_adsi) + self.assertEqual(1, ApproximateDirectoryContentIndex.objects.all().count()) + self.assertEqual(1, ApproximateDirectoryStructureIndex.objects.all().count()) + + # Check to see if the correct values have been indexed + adci = ApproximateDirectoryContentIndex.objects.all()[0] + adsi = ApproximateDirectoryStructureIndex.objects.all()[0] + + expected_adci_fingerprint = '0000000288212131028101000400403044049614' + expected_adsi_fingerprint = '00000002160440008028c38c24a8038040006040' + self.assertEqual(expected_adci_fingerprint, adci.fingerprint()) + self.assertEqual(expected_adsi_fingerprint, adsi.fingerprint()) diff --git a/matchcode/tests/matchcode/test_match.py b/matchcode/tests/matchcode/test_match.py new file mode 100644 index 00000000..2fe19b75 --- /dev/null +++ b/matchcode/tests/matchcode/test_match.py @@ -0,0 +1,333 @@ +# +# Copyright (c) 2017 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# + +import os + +import attr +from commoncode.resource import VirtualCodebase +from packagedb.models import Package + +from matchcode.fingerprinting import compute_directory_fingerprints +from matchcode.management.commands.index_packages import index_package_directories +from matchcode.match import EXACT_PACKAGE_ARCHIVE_MATCH +from matchcode.match import APPROXIMATE_DIRECTORY_STRUCTURE_MATCH +from matchcode.match import APPROXIMATE_DIRECTORY_CONTENT_MATCH +from matchcode.match import EXACT_FILE_MATCH +from matchcode.match import do_match +from matchcode.match import path_suffixes +from matchcode.models import get_or_create_indexable_package +from matchcode.utils import index_package_files_sha1 +from matchcode.utils import index_packages_sha1 +from matchcode.utils import load_resources_from_scan +from matchcode.utils import MatchcodeTestCase + + +def run_do_match_from_scan(scan_file_location, match_type): + vc = VirtualCodebase( + location=scan_file_location, + codebase_attributes=dict( + matches=attr.ib(default=attr.Factory(list)) + ), + resource_attributes=dict( + matched_to=attr.ib(default=attr.Factory(list)) + ) + ) + vc = compute_directory_fingerprints(vc) + do_match(vc, match_type) + return vc + + +class MatchPackagesTestCase(MatchcodeTestCase): + BASE_DIR = os.path.join(os.path.dirname(__file__), 'testfiles') + maxDiff = None + + def setUp(self): + # Execute the superclass' setUp method before creating our own + # DB objects + super(MatchPackagesTestCase, self).setUp() + + self.test_package1, _ = Package.objects.get_or_create( + filename='abbot-0.12.3.jar', + sha1='51d28a27d919ce8690a40f4f335b9d591ceb16e9', + md5='38206e62a54b0489fb6baa4db5a06093', + size=689791, + name='abbot', + version='0.12.3', + download_url='http://repo1.maven.org/maven2/abbot/abbot/0.12.3/abbot-0.12.3.jar', + type='maven', + ) + self.test_package1_metadata = self.test_package1.to_dict() + + self.test_package2, _ = Package.objects.get_or_create( + filename='dojoz-0.4.1-1.jar', + sha1='ae9d68fd6a29906606c2d9407d1cc0749ef84588', + md5='508361a1c6273a4c2b8e4945618b509f', + size=876720, + name='dojoz', + version='0.4.1-1', + download_url='https://repo1.maven.org/maven2/org/zkoss/zkforge/dojoz/0.4.1-1/dojoz-0.4.1-1.jar', + type='maven', + ) + self.test_package2_metadata = self.test_package2.to_dict() + + self.test_package3, _ = Package.objects.get_or_create( + filename='acegi-security-0.51.jar', + sha1='ede156692b33872f5ee9465b7a06d6b2bc9e5e7f', + size=176954, + name='acegi-security', + version='0.51', + download_url='https://repo1.maven.org/maven2/acegisecurity/acegi-security/0.51/acegi-security-0.51.jar', + type='maven' + ) + self.test_package3_metadata = self.test_package3.to_dict() + + self.test_package4, _ = Package.objects.get_or_create( + filename='test.tar.gz', + sha1='deadbeef', + size=42589, + name='test', + version='0.01', + download_url='https://test.com/test.tar.gz', + type='maven' + ) + self.test_package4_metadata = self.test_package4.to_dict() + self.test_indexable_package4, _ = get_or_create_indexable_package(self.test_package4) + + # Populate ExactPackageArchiveIndexFingerprint table + index_packages_sha1() + + load_resources_from_scan(self.get_test_loc('models/match-test.json'), self.test_package4) + index_package_directories(self.test_indexable_package4) + index_package_files_sha1(self.test_package4, self.get_test_loc('models/match-test.json')) + + def test_do_match_package_archive_match(self): + input_file = self.get_test_loc('models/match-test.json') + vc = run_do_match_from_scan(input_file, EXACT_PACKAGE_ARCHIVE_MATCH) + expected = self.get_test_loc('models/match-test-exact-package-results.json') + self.check_codebase(vc, expected, regen=False) + + def test_do_match_approximate_directory_structure_match(self): + input_file = self.get_test_loc('models/match-test.json') + vc = run_do_match_from_scan(input_file, APPROXIMATE_DIRECTORY_STRUCTURE_MATCH) + expected = self.get_test_loc('models/match-test-approximate-directory-structure-results.json') + self.check_codebase(vc, expected, regen=False) + + def test_do_match_approximate_directory_content_match(self): + input_file = self.get_test_loc('models/match-test.json') + vc = run_do_match_from_scan(input_file, APPROXIMATE_DIRECTORY_CONTENT_MATCH) + expected = self.get_test_loc('models/match-test-approximate-directory-content-results.json') + self.check_codebase(vc, expected, regen=False) + + def test_do_match_package_file_match(self): + input_file = self.get_test_loc('models/match-test.json') + vc = run_do_match_from_scan(input_file, EXACT_FILE_MATCH) + expected = self.get_test_loc('models/match-test-exact-file-results.json') + self.check_codebase(vc, expected, regen=False) + + +class MatchNestedPackagesTestCase(MatchcodeTestCase): + BASE_DIR = os.path.join(os.path.dirname(__file__), 'testfiles') + maxDiff = None + + def setUp(self): + # Execute the superclass' setUp method before creating our own + # DB objects + super(MatchNestedPackagesTestCase, self).setUp() + + self.test_package1, _ = Package.objects.get_or_create( + filename='plugin-request-2.4.1.tgz', + sha1='7295749caddd3c52be472eef6623a7b441ed17d6', + size=7269, + name='plugin-request', + version='2.4.1', + download_url='https://registry.npmjs.org/@umijs/plugin-request/-/plugin-request-2.4.1.tgz', + type='npm', + ) + load_resources_from_scan(self.get_test_loc('match/nested/plugin-request-2.4.1-ip.json'), self.test_package1) + self.test_indexable_package1, _ = get_or_create_indexable_package(self.test_package1) + index_package_directories(self.test_indexable_package1) + + self.test_package2, _ = Package.objects.get_or_create( + filename='underscore-1.10.9.tgz', + sha1='ba7a9cfc15873e67821611503a34a7c26bf7264f', + size=26569, + name='underscore', + version='1.10.9', + download_url='https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz', + type='npm', + ) + load_resources_from_scan(self.get_test_loc('match/nested/underscore-1.10.9-ip.json'), self.test_package2) + self.test_indexable_package2, _ = get_or_create_indexable_package(self.test_package2) + index_package_directories(self.test_indexable_package2) + + def test_do_match_approximate_directory_structure_match(self): + input_file = self.get_test_loc('match/nested/nested.json') + vc = run_do_match_from_scan(input_file, APPROXIMATE_DIRECTORY_STRUCTURE_MATCH) + expected = self.get_test_loc('match/nested/nested-expected.json') + self.check_codebase(vc, expected, regen=False) + + +class MatchUtilityFunctionsTestCase(MatchcodeTestCase): + def test_path_suffixes(self): + suffixes = list(path_suffixes('/foo/bar/baz/qux')) + expected = ['foo/bar/baz/qux', 'bar/baz/qux', 'baz/qux', 'qux'] + self.assertEqual(expected, suffixes) + + +class DirectoryMatchingTestCase(MatchcodeTestCase): + BASE_DIR = os.path.join(os.path.dirname(__file__), 'testfiles') + maxDiff = None + + def setUp(self): + super(DirectoryMatchingTestCase, self).setUp() + + self.test_package1, _ = Package.objects.get_or_create( + filename='abbrev-1.0.3.tgz', + sha1='aa049c967f999222aa42e14434f0c562ef468241', + name='abbrev', + version='1.0.3', + type='npm', + download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.0.3.tgz', + ) + load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.0.3-i.json'), self.test_package1) + self.test_indexable_package1, _ = get_or_create_indexable_package(self.test_package1) + index_package_directories(self.test_indexable_package1) + + self.test_package2, _ = Package.objects.get_or_create( + filename='abbrev-1.0.4.tgz', + sha1='bd55ae5e413ba1722ee4caba1f6ea10414a59ecd', + name='abbrev', + version='1.0.4', + type='npm', + download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.0.4.tgz', + ) + load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.0.4-i.json'), self.test_package2) + self.test_indexable_package2, _ = get_or_create_indexable_package(self.test_package2) + index_package_directories(self.test_indexable_package2) + + self.test_package3, _ = Package.objects.get_or_create( + filename='abbrev-1.0.5.tgz', + sha1='5d8257bd9ebe435e698b2fa431afde4fe7b10b03', + name='abbrev', + version='1.0.5', + type='npm', + download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.0.5.tgz', + ) + load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.0.5-i.json'), self.test_package3) + self.test_indexable_package3, _ = get_or_create_indexable_package(self.test_package3) + index_package_directories(self.test_indexable_package3) + + self.test_package4, _ = Package.objects.get_or_create( + filename='abbrev-1.0.6.tgz', + sha1='b6d632b859b3fa2d6f7e4b195472461b9e32dc30', + name='abbrev', + version='1.0.6', + type='npm', + download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.0.6.tgz', + ) + load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.0.6-i.json'), self.test_package4) + self.test_indexable_package4, _ = get_or_create_indexable_package(self.test_package4) + index_package_directories(self.test_indexable_package4) + + self.test_package5, _ = Package.objects.get_or_create( + filename='abbrev-1.0.7.tgz', + sha1='5b6035b2ee9d4fb5cf859f08a9be81b208491843', + name='abbrev', + version='1.0.7', + type='npm', + download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz', + ) + load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.0.7-i.json'), self.test_package5) + self.test_indexable_package5, _ = get_or_create_indexable_package(self.test_package5) + index_package_directories(self.test_indexable_package5) + + self.test_package6, _ = Package.objects.get_or_create( + filename='abbrev-1.0.9.tgz', + sha1='91b4792588a7738c25f35dd6f63752a2f8776135', + name='abbrev', + version='1.0.9', + type='npm', + download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz', + ) + load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.0.9-i.json'), self.test_package6) + self.test_indexable_package6, _ = get_or_create_indexable_package(self.test_package6) + index_package_directories(self.test_indexable_package6) + + self.test_package7, _ = Package.objects.get_or_create( + filename='abbrev-1.1.0.tgz', + sha1='d0554c2256636e2f56e7c2e5ad183f859428d81f', + name='abbrev', + version='1.1.0', + type='npm', + download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz', + ) + load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.1.0-i.json'), self.test_package7) + self.test_indexable_package7, _ = get_or_create_indexable_package(self.test_package7) + index_package_directories(self.test_indexable_package7) + + self.test_package8, _ = Package.objects.get_or_create( + filename='abbrev-1.1.1.tgz', + sha1='f8f2c887ad10bf67f634f005b6987fed3179aac8', + name='abbrev', + version='1.1.1', + type='npm', + download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz', + ) + load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.1.1-i.json'), self.test_package8) + self.test_indexable_package8, _ = get_or_create_indexable_package(self.test_package8) + index_package_directories(self.test_indexable_package8) + + def test_match_ApproximateDirectoryStructureIndex_abbrev_1_0_3(self): + input_file = self.get_test_loc('match/directory-matching/abbrev-1.0.3-i.json') + vc = run_do_match_from_scan(input_file, APPROXIMATE_DIRECTORY_STRUCTURE_MATCH) + expected = self.get_test_loc('match/directory-matching/abbrev-1.0.3-i-expected.json') + self.check_codebase(vc, expected, regen=False) + + def test_match_ApproximateDirectoryStructureIndex_abbrev_1_0_4(self): + input_file = self.get_test_loc('match/directory-matching/abbrev-1.0.4-i.json') + vc = run_do_match_from_scan(input_file, APPROXIMATE_DIRECTORY_STRUCTURE_MATCH) + expected = self.get_test_loc('match/directory-matching/abbrev-1.0.4-i-expected.json') + self.check_codebase(vc, expected, regen=False) + + def test_match_ApproximateDirectoryStructureIndex_abbrev_1_0_5(self): + input_file = self.get_test_loc('match/directory-matching/abbrev-1.0.5-i.json') + vc = run_do_match_from_scan(input_file, APPROXIMATE_DIRECTORY_STRUCTURE_MATCH) + expected = self.get_test_loc('match/directory-matching/abbrev-1.0.5-i-expected.json') + self.check_codebase(vc, expected, regen=False) + + def test_match_ApproximateDirectoryStructureIndex_abbrev_1_0_6(self): + input_file = self.get_test_loc('match/directory-matching/abbrev-1.0.6-i.json') + vc = run_do_match_from_scan(input_file, APPROXIMATE_DIRECTORY_STRUCTURE_MATCH) + expected = self.get_test_loc('match/directory-matching/abbrev-1.0.6-i-expected.json') + self.check_codebase(vc, expected, regen=False) + + def test_match_ApproximateDirectoryStructureIndex_abbrev_1_0_7(self): + input_file = self.get_test_loc('match/directory-matching/abbrev-1.0.7-i.json') + vc = run_do_match_from_scan(input_file, APPROXIMATE_DIRECTORY_STRUCTURE_MATCH) + expected = self.get_test_loc('match/directory-matching/abbrev-1.0.7-i-expected.json') + self.check_codebase(vc, expected, regen=False) + + def test_match_ApproximateDirectoryStructureIndex_abbrev_1_0_9(self): + input_file = self.get_test_loc('match/directory-matching/abbrev-1.0.9-i.json') + vc = run_do_match_from_scan(input_file, APPROXIMATE_DIRECTORY_STRUCTURE_MATCH) + expected = self.get_test_loc('match/directory-matching/abbrev-1.0.9-i-expected.json') + self.check_codebase(vc, expected, regen=False) + + def test_match_ApproximateDirectoryStructureIndex_abbrev_1_1_0(self): + input_file = self.get_test_loc('match/directory-matching/abbrev-1.1.0-i.json') + vc = run_do_match_from_scan(input_file, APPROXIMATE_DIRECTORY_STRUCTURE_MATCH) + expected = self.get_test_loc('match/directory-matching/abbrev-1.1.0-i-expected.json') + self.check_codebase(vc, expected, regen=False) + + def test_match_ApproximateDirectoryStructureIndex_abbrev_1_1_1(self): + input_file = self.get_test_loc('match/directory-matching/abbrev-1.1.1-i.json') + vc = run_do_match_from_scan(input_file, APPROXIMATE_DIRECTORY_STRUCTURE_MATCH) + expected = self.get_test_loc('match/directory-matching/abbrev-1.1.1-i-expected.json') + self.check_codebase(vc, expected, regen=False) + + def test_match_ApproximateDirectoryStructureIndex_get_stdin_3_0_2(self): + input_file = self.get_test_loc('match/directory-matching/get-stdin-3.0.2-i.json') + vc = run_do_match_from_scan(input_file, APPROXIMATE_DIRECTORY_STRUCTURE_MATCH) + expected = self.get_test_loc('match/directory-matching/get-stdin-3.0.2-i-expected.json') + self.check_codebase(vc, expected, regen=False) diff --git a/matchcode/tests/matchcode/test_models.py b/matchcode/tests/matchcode/test_models.py new file mode 100644 index 00000000..91f279e8 --- /dev/null +++ b/matchcode/tests/matchcode/test_models.py @@ -0,0 +1,223 @@ +# +# Copyright (c) 2017 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# + +import os + +from commoncode.resource import VirtualCodebase +from packagedb.models import Package +import attr + +from matchcode.fingerprinting import compute_directory_fingerprints +from matchcode.management.commands.index_packages import index_package_directories +from matchcode.models import ApproximateDirectoryContentIndex +from matchcode.models import ApproximateDirectoryStructureIndex +from matchcode.models import create_halohash_chunks +from matchcode.models import get_or_create_indexable_package +from matchcode.models import hexstring_to_binarray +from matchcode.models import ExactPackageArchiveIndex +from matchcode.models import ExactFileIndex +from matchcode.utils import index_packages_sha1 +from matchcode.utils import index_package_files_sha1 +from matchcode.utils import load_resources_from_scan +from matchcode.utils import MatchcodeTestCase + + +EXACT_PACKAGE_ARCHIVE_MATCH = 0 +APPROXIMATE_DIRECTORY_STRUCTURE_MATCH = 1 +APPROXIMATE_DIRECTORY_CONTENT_MATCH = 2 +EXACT_FILE_MATCH = 3 + + +class BaseModelTest(MatchcodeTestCase): + BASE_DIR = os.path.join(os.path.dirname(__file__), 'testfiles') + maxDiff = None + + def setUp(self): + super(BaseModelTest, self).setUp() + + self.test_package1, _ = Package.objects.get_or_create( + filename='abbot-0.12.3.jar', + sha1='51d28a27d919ce8690a40f4f335b9d591ceb16e9', + md5='38206e62a54b0489fb6baa4db5a06093', + size=689791, + name='abbot', + version='0.12.3', + download_url='http://repo1.maven.org/maven2/abbot/abbot/0.12.3/abbot-0.12.3.jar', + type='maven', + ) + self.test_package1_metadata = self.test_package1.to_dict() + + self.test_package2, _ = Package.objects.get_or_create( + filename='dojoz-0.4.1-1.jar', + sha1='ae9d68fd6a29906606c2d9407d1cc0749ef84588', + md5='508361a1c6273a4c2b8e4945618b509f', + size=876720, + name='dojoz', + version='0.4.1-1', + download_url='https://repo1.maven.org/maven2/org/zkoss/zkforge/dojoz/0.4.1-1/dojoz-0.4.1-1.jar', + type='maven', + ) + self.test_package2_metadata = self.test_package2.to_dict() + + self.test_package3, _ = Package.objects.get_or_create( + filename='acegi-security-0.51.jar', + sha1='ede156692b33872f5ee9465b7a06d6b2bc9e5e7f', + size=176954, + name='acegi-security', + version='0.51', + download_url='https://repo1.maven.org/maven2/acegisecurity/acegi-security/0.51/acegi-security-0.51.jar', + type='maven' + ) + self.test_package3_metadata = self.test_package3.to_dict() + + self.test_package4, _ = Package.objects.get_or_create( + filename='test.tar.gz', + sha1='deadbeef', + size=42589, + name='test', + version='0.01', + download_url='https://test.com/test.tar.gz', + type='maven' + ) + self.test_package4_metadata = self.test_package4.to_dict() + self.test_indexable_package4, _ = get_or_create_indexable_package(self.test_package4) + + # Populate ExactPackageArchiveIndexFingerprint table + index_packages_sha1() + + # Populate ExactFileIndexFingerprint table + load_resources_from_scan(self.get_test_loc('models/match-test.json'), self.test_package4) + index_package_directories(self.test_indexable_package4) + index_package_files_sha1(self.test_package4, self.get_test_loc('models/match-test.json')) + + +class ExactPackageArchiveIndexModelTestCase(BaseModelTest): + def test_ExactPackageArchiveIndex_single_sha1_single_match(self): + result = ExactPackageArchiveIndex.match('51d28a27d919ce8690a40f4f335b9d591ceb16e9') + result = [r.package.package.to_dict() for r in result] + expected = [self.test_package1_metadata] + self.assertEqual(expected, result) + + +class ExactFileIndexModelTestCase(BaseModelTest): + def test_ExactFileIndex_match(self): + scan_location = self.get_test_loc('models/match-test.json') + codebase = VirtualCodebase( + location=scan_location, + codebase_attributes=dict( + matches=attr.ib(default=attr.Factory(list)) + ), + resource_attributes=dict( + matched_to=attr.ib(default=attr.Factory(list)) + ) + ) + + # populate codebase with match results + for resource in codebase.walk(topdown=True): + matches = ExactFileIndex.match(resource.sha1) + for match in matches: + p = match.package.package.to_dict() + p['match_type'] = 'exact' + codebase.attributes.matches.append(p) + resource.matched_to.append(p['purl']) + resource.save(codebase) + + expected = self.get_test_loc('models/exact-file-matching-standalone-test-results.json') + self.check_codebase(codebase, expected, regen=False) + + +class ApproximateDirectoryMatchingIndexModelTestCase(MatchcodeTestCase): + BASE_DIR = os.path.join(os.path.dirname(__file__), 'testfiles') + + def setUp(self): + super(MatchcodeTestCase, self).setUp() + self.test_package1, _ = Package.objects.get_or_create( + filename='async-0.2.10.tgz', + sha1='b6bbe0b0674b9d719708ca38de8c237cb526c3d1', + md5='fd313a0e8cc2343569719e80cd7a67ac', + size=15772, + name='async', + version='0.2.10', + download_url='https://registry.npmjs.org/async/-/async-0.2.10.tgz', + type='npm', + ) + self.test_package1_metadata = self.test_package1.to_dict() + load_resources_from_scan(self.get_test_loc('models/directory-matching/async-0.2.10.tgz-i.json'), self.test_package1) + self.test_indexablepackage1, _ = get_or_create_indexable_package(self.test_package1) + index_package_directories(self.test_indexablepackage1) + + self.test_package2, _ = Package.objects.get_or_create( + filename='async-0.2.9.tgz', + sha1='df63060fbf3d33286a76aaf6d55a2986d9ff8619', + md5='895ac62ba7c61086cffdd50ab03c0447', + size=15672, + name='async', + version='0.2.9', + download_url='https://registry.npmjs.org/async/-/async-0.2.9.tgz', + type='npm', + ) + self.test_package2_metadata = self.test_package2.to_dict() + load_resources_from_scan(self.get_test_loc('models/directory-matching/async-0.2.9-i.json'), self.test_package2) + self.test_indexablepackage2, _ = get_or_create_indexable_package(self.test_package2) + index_package_directories(self.test_indexablepackage2) + + def test_ApproximateDirectoryStructureIndex_match_subdir(self): + scan_location = self.get_test_loc('models/directory-matching/async-0.2.9-i.json') + vc = VirtualCodebase( + location=scan_location, + resource_attributes=dict(packages=attr.ib(default=attr.Factory(list))) + ) + codebase = compute_directory_fingerprints(vc) + + # populate codebase with match results + for resource in codebase.walk(topdown=True): + if resource.is_file: + continue + fp = resource.extra_data.get('directory_structure', '') + matches = ApproximateDirectoryStructureIndex.match(fp) + for match in matches: + p = match.package.package.to_dict() + p['match_type'] = 'approximate-directory-structure' + resource.packages.append(p) + resource.save(codebase) + + expected = self.get_test_loc('models/directory-matching/async-0.2.9-i-expected-structure.json') + self.check_codebase(codebase, expected, regen=False) + + def test_ApproximateDirectoryContentIndex_match_subdir(self): + scan_location = self.get_test_loc('models/directory-matching/async-0.2.9-i.json') + vc = VirtualCodebase( + location=scan_location, + resource_attributes=dict(packages=attr.ib(default=attr.Factory(list))) + ) + codebase = compute_directory_fingerprints(vc) + + # populate codebase with match results + for resource in codebase.walk(topdown=True): + if resource.is_file: + continue + fp = resource.extra_data.get('directory_content', '') + matches = ApproximateDirectoryContentIndex.match(fp) + for match in matches: + p = match.package.package.to_dict() + p['match_type'] = 'approximate-directory-content' + resource.packages.append(p) + resource.save(codebase) + + expected = self.get_test_loc('models/directory-matching/async-0.2.9-i-expected-content.json') + self.check_codebase(codebase, expected, regen=False) + + +class MatchcodeModelUtilsTestCase(MatchcodeTestCase): + def test_create_halohash_chunks(self): + fingerprint = '49280e141724c001e1080128621a4210' + chunk1, chunk2, chunk3, chunk4 = create_halohash_chunks(fingerprint) + expected_chunk1 = hexstring_to_binarray('49280e14') + expected_chunk2 = hexstring_to_binarray('1724c001') + expected_chunk3 = hexstring_to_binarray('e1080128') + expected_chunk4 = hexstring_to_binarray('621a4210') + self.assertEqual(expected_chunk1, chunk1) + self.assertEqual(expected_chunk2, chunk2) + self.assertEqual(expected_chunk3, chunk3) + self.assertEqual(expected_chunk4, chunk4) diff --git a/matchcode/tests/matchcode/testfiles/api/scan2_match_results.json b/matchcode/tests/matchcode/testfiles/api/scan2_match_results.json new file mode 100644 index 00000000..90f7d263 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/api/scan2_match_results.json @@ -0,0 +1,235 @@ +{ + "packages": [ + { + "type": "maven", + "namespace": "", + "name": "test", + "version": "0.01", + "qualifiers": "", + "subpath": "", + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": "https://test.com/test.tar.gz", + "size": 42589, + "sha1": "deadbeef", + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "license_expression": null, + "declared_license": null, + "notice_text": null, + "root_path": null, + "dependencies": [], + "contains_source_code": null, + "source_packages": [], + "package_url": "pkg:maven/test@0.01", + "match_type": "approximate-directory" + } + ], + "files": [ + { + "path": "test", + "type": "directory", + "name": "test", + "base_name": "test", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "for_packages": [ + "pkg:maven/test@0.01" + ], + "files_count": 4, + "dirs_count": 2, + "size_count": 1743469, + "scan_errors": [] + }, + { + "path": "test/c", + "type": "file", + "name": "c", + "base_name": "c", + "extension": "", + "size": 4, + "date": "2020-06-19", + "sha1": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", + "md5": "098f6bcd4621d373cade4e832627b4f6", + "mime_type": "text/plain", + "file_type": "ASCII text, with no line terminators", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "for_packages": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/a", + "type": "directory", + "name": "a", + "base_name": "a", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "for_packages": [ + "pkg:maven/test@0.01" + ], + "files_count": 2, + "dirs_count": 0, + "size_count": 1053674, + "scan_errors": [] + }, + { + "path": "test/a/acegi-security-0.51.jar", + "type": "file", + "name": "acegi-security-0.51.jar", + "base_name": "acegi-security-0.51", + "extension": ".jar", + "size": 176954, + "date": "2020-06-19", + "sha1": "ede156692b33872f5ee9465b7a06d6b2bc9e5e7f", + "md5": "19dad3908042b2bdc50cbfdaed7da200", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "for_packages": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/a/dojoz-0.4.1-1.jar", + "type": "file", + "name": "dojoz-0.4.1-1.jar", + "base_name": "dojoz-0.4.1-1", + "extension": ".jar", + "size": 876720, + "date": "2020-06-19", + "sha1": "ae9d68fd6a29906606c2d9407d1cc0749ef84588", + "md5": "508361a1c6273a4c2b8e4945618b509f", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "for_packages": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/b", + "type": "directory", + "name": "b", + "base_name": "b", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "for_packages": [ + "pkg:maven/test@0.01" + ], + "files_count": 1, + "dirs_count": 0, + "size_count": 689791, + "scan_errors": [] + }, + { + "path": "test/b/abbot-0.12.3.jar", + "type": "file", + "name": "abbot-0.12.3.jar", + "base_name": "abbot-0.12.3", + "extension": ".jar", + "size": 689791, + "date": "2020-05-27", + "sha1": "51d28a27d919ce8690a40f4f335b9d591ceb16e9", + "md5": "38206e62a54b0489fb6baa4db5a06093", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "for_packages": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/fingerprinting/abbrev-1.0.3-i.json b/matchcode/tests/matchcode/testfiles/fingerprinting/abbrev-1.0.3-i.json new file mode 100644 index 00000000..2d418ac9 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/fingerprinting/abbrev-1.0.3-i.json @@ -0,0 +1,161 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "31.2.2", + "options": { + "input": [ + "package" + ], + "--info": true, + "--json-pp": "./abbrev-1.0.3.tgz-i.json" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2022-12-02T011500.811761", + "end_timestamp": "2022-12-02T011501.090542", + "output_format_version": "2.0.0", + "duration": 0.2787973880767822, + "message": null, + "errors": [], + "warnings": [], + "extra_data": { + "system_environment": { + "operating_system": "linux", + "cpu_architecture": "64", + "platform": "Linux-5.15.39-3-pve-x86_64-with-glibc2.35", + "platform_version": "#2 SMP PVE 5.15.39-3 (Wed, 27 Jul 2022 13:45:39 +0200)", + "python_version": "3.10.5 (main, Jul 30 2022, 06:09:26) [GCC 9.4.0]" + }, + "spdx_license_list_version": "3.17", + "files_count": 3 + } + } + ], + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 3, + "dirs_count": 1, + "size_count": 3358, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 277, + "date": "2011-03-24", + "sha1": "d61dc2c98ab10bf909b99f60e7bf584a7f7ead8c", + "md5": "8468753cba56d0075f6532a657ee5821", + "sha256": "5ab100bf0eb08adb175db170a1254d14e0be705ff1b563e5acddd3c8d03faee1", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2011-03-24", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/lib", + "type": "directory", + "name": "lib", + "base_name": "lib", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 1, + "dirs_count": 0, + "size_count": 2582, + "scan_errors": [] + }, + { + "path": "package/lib/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 2582, + "date": "2011-03-24", + "sha1": "055ec01ac8b111bc948e498d87d9dc47f5e5acaa", + "md5": "06aebeadc85e52f4b8bf88eab6cd8b6c", + "sha256": "efd2c9b755dc4b2df3231222b5b6a63b7a1343472dfbc8807c5f15e1d28a0c75", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.3-i-expected.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.3-i-expected.json new file mode 100644 index 00000000..c8bb9dcb --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.3-i-expected.json @@ -0,0 +1,144 @@ +{ + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.3" + ], + "files_count": 3, + "dirs_count": 1, + "size_count": 3358, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 277, + "date": "2011-03-24", + "sha1": "d61dc2c98ab10bf909b99f60e7bf584a7f7ead8c", + "md5": "8468753cba56d0075f6532a657ee5821", + "sha256": "5ab100bf0eb08adb175db170a1254d14e0be705ff1b563e5acddd3c8d03faee1", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.3" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2011-03-24", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.3" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/lib", + "type": "directory", + "name": "lib", + "base_name": "lib", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.3" + ], + "files_count": 1, + "dirs_count": 0, + "size_count": 2582, + "scan_errors": [] + }, + { + "path": "package/lib/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 2582, + "date": "2011-03-24", + "sha1": "055ec01ac8b111bc948e498d87d9dc47f5e5acaa", + "md5": "06aebeadc85e52f4b8bf88eab6cd8b6c", + "sha256": "efd2c9b755dc4b2df3231222b5b6a63b7a1343472dfbc8807c5f15e1d28a0c75", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.3" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.3-i.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.3-i.json new file mode 100644 index 00000000..2d418ac9 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.3-i.json @@ -0,0 +1,161 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "31.2.2", + "options": { + "input": [ + "package" + ], + "--info": true, + "--json-pp": "./abbrev-1.0.3.tgz-i.json" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2022-12-02T011500.811761", + "end_timestamp": "2022-12-02T011501.090542", + "output_format_version": "2.0.0", + "duration": 0.2787973880767822, + "message": null, + "errors": [], + "warnings": [], + "extra_data": { + "system_environment": { + "operating_system": "linux", + "cpu_architecture": "64", + "platform": "Linux-5.15.39-3-pve-x86_64-with-glibc2.35", + "platform_version": "#2 SMP PVE 5.15.39-3 (Wed, 27 Jul 2022 13:45:39 +0200)", + "python_version": "3.10.5 (main, Jul 30 2022, 06:09:26) [GCC 9.4.0]" + }, + "spdx_license_list_version": "3.17", + "files_count": 3 + } + } + ], + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 3, + "dirs_count": 1, + "size_count": 3358, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 277, + "date": "2011-03-24", + "sha1": "d61dc2c98ab10bf909b99f60e7bf584a7f7ead8c", + "md5": "8468753cba56d0075f6532a657ee5821", + "sha256": "5ab100bf0eb08adb175db170a1254d14e0be705ff1b563e5acddd3c8d03faee1", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2011-03-24", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/lib", + "type": "directory", + "name": "lib", + "base_name": "lib", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 1, + "dirs_count": 0, + "size_count": 2582, + "scan_errors": [] + }, + { + "path": "package/lib/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 2582, + "date": "2011-03-24", + "sha1": "055ec01ac8b111bc948e498d87d9dc47f5e5acaa", + "md5": "06aebeadc85e52f4b8bf88eab6cd8b6c", + "sha256": "efd2c9b755dc4b2df3231222b5b6a63b7a1343472dfbc8807c5f15e1d28a0c75", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.4-i-expected.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.4-i-expected.json new file mode 100644 index 00000000..eaf563c1 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.4-i-expected.json @@ -0,0 +1,172 @@ +{ + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.4" + ], + "files_count": 4, + "dirs_count": 1, + "size_count": 4750, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 1092, + "date": "2011-05-13", + "sha1": "4a1927e74796f06ac7e7a687ca6b44d39c65d8f4", + "md5": "b3245a33f2d41818f14c489bd33bc4a8", + "sha256": "ab95d69fc43021e0451b5021ff09fbc89ee6f9199354397f18fe7ce3f4b50554", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.4" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 390, + "date": "2013-01-09", + "sha1": "2832a2ddeaff7e4e7960926364afd12af09b24ba", + "md5": "91fb59a2c07fe8957b2ff90ccdf763dc", + "sha256": "e46d8ebcf41a23aa903a2562ae072e4dee4371924d8df21a9fa0ebff318f2b7d", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.4" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2010-10-04", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.4" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/lib", + "type": "directory", + "name": "lib", + "base_name": "lib", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.4" + ], + "files_count": 1, + "dirs_count": 0, + "size_count": 2769, + "scan_errors": [] + }, + { + "path": "package/lib/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 2769, + "date": "2013-01-09", + "sha1": "3bbe6fc1ec9637b4b479944a2c07e56e5af781d4", + "md5": "603580683bf579f239e07d213da29a0b", + "sha256": "6dbd26a673dd839cd7e0dc0d9d28dd6a80373ca7d114400d47ca7aa68bb703bb", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.4" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.4-i.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.4-i.json new file mode 100644 index 00000000..f82e3c2f --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.4-i.json @@ -0,0 +1,186 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "31.2.2", + "options": { + "input": [ + "package" + ], + "--info": true, + "--json-pp": "./abbrev-1.0.4.tgz-i.json" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2022-12-02T011502.218997", + "end_timestamp": "2022-12-02T011502.489555", + "output_format_version": "2.0.0", + "duration": 0.27057909965515137, + "message": null, + "errors": [], + "warnings": [], + "extra_data": { + "system_environment": { + "operating_system": "linux", + "cpu_architecture": "64", + "platform": "Linux-5.15.39-3-pve-x86_64-with-glibc2.35", + "platform_version": "#2 SMP PVE 5.15.39-3 (Wed, 27 Jul 2022 13:45:39 +0200)", + "python_version": "3.10.5 (main, Jul 30 2022, 06:09:26) [GCC 9.4.0]" + }, + "spdx_license_list_version": "3.17", + "files_count": 4 + } + } + ], + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 4, + "dirs_count": 1, + "size_count": 4750, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 1092, + "date": "2011-05-13", + "sha1": "4a1927e74796f06ac7e7a687ca6b44d39c65d8f4", + "md5": "b3245a33f2d41818f14c489bd33bc4a8", + "sha256": "ab95d69fc43021e0451b5021ff09fbc89ee6f9199354397f18fe7ce3f4b50554", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 390, + "date": "2013-01-09", + "sha1": "2832a2ddeaff7e4e7960926364afd12af09b24ba", + "md5": "91fb59a2c07fe8957b2ff90ccdf763dc", + "sha256": "e46d8ebcf41a23aa903a2562ae072e4dee4371924d8df21a9fa0ebff318f2b7d", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2010-10-04", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/lib", + "type": "directory", + "name": "lib", + "base_name": "lib", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 1, + "dirs_count": 0, + "size_count": 2769, + "scan_errors": [] + }, + { + "path": "package/lib/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 2769, + "date": "2013-01-09", + "sha1": "3bbe6fc1ec9637b4b479944a2c07e56e5af781d4", + "md5": "603580683bf579f239e07d213da29a0b", + "sha256": "6dbd26a673dd839cd7e0dc0d9d28dd6a80373ca7d114400d47ca7aa68bb703bb", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.5-i-expected.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.5-i-expected.json new file mode 100644 index 00000000..2d346605 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.5-i-expected.json @@ -0,0 +1,200 @@ +{ + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.5" + ], + "files_count": 6, + "dirs_count": 0, + "size_count": 4925, + "scan_errors": [] + }, + { + "path": "package/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 1764, + "date": "2014-04-17", + "sha1": "b75c6b10bbfac1092ef493079ae044cc89824dc0", + "md5": "7a15b8fe67321134796aa0efe08015d0", + "sha256": "17c7c4c5ba278eacdf05f8e62243edda7036c39f4b61448aa753c77b078a11ed", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.5" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/CONTRIBUTING.md", + "type": "file", + "name": "CONTRIBUTING.md", + "base_name": "CONTRIBUTING", + "extension": ".md", + "size": 123, + "date": "2013-02-12", + "sha1": "12ce7abccdd3aeebd1d093a30bb1768f120fb8cb", + "md5": "390ff2e45c4ab33b721d10a45c147c0c", + "sha256": "a654f59a0ffa0d17296a230c512790f66144a18e410222cab9bfe3a00fd8b7e8", + "mime_type": "text/html", + "file_type": "HTML document, ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.5" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 1092, + "date": "2011-05-13", + "sha1": "4a1927e74796f06ac7e7a687ca6b44d39c65d8f4", + "md5": "b3245a33f2d41818f14c489bd33bc4a8", + "sha256": "ab95d69fc43021e0451b5021ff09fbc89ee6f9199354397f18fe7ce3f4b50554", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.5" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 378, + "date": "2014-04-17", + "sha1": "bb9785300f276c536e35e4050aa06e93fed3e0aa", + "md5": "cb4d65ccc150047accc4feab2875697f", + "sha256": "cd8af8b13b7f5f0072c3fcbef24a341fd314e667a8d3994d22553df14b1fb5f5", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.5" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2010-10-04", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.5" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/test.js", + "type": "file", + "name": "test.js", + "base_name": "test", + "extension": ".js", + "size": 1069, + "date": "2014-04-17", + "sha1": "69ec8e64451d6fbcc2d53717c5f3a630bd0432d8", + "md5": "245853103cf43465e31ac1b255edbfed", + "sha256": "34a78de842db494b0ff8ad4574a93cbbc4bf6e74cb064ab0f6bab6d11b80833a", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.5" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.5-i.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.5-i.json new file mode 100644 index 00000000..3eb790bc --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.5-i.json @@ -0,0 +1,211 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "31.2.2", + "options": { + "input": [ + "package" + ], + "--info": true, + "--json-pp": "./abbrev-1.0.5.tgz-i.json" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2022-12-02T011503.577713", + "end_timestamp": "2022-12-02T011503.769749", + "output_format_version": "2.0.0", + "duration": 0.19205188751220703, + "message": null, + "errors": [], + "warnings": [], + "extra_data": { + "system_environment": { + "operating_system": "linux", + "cpu_architecture": "64", + "platform": "Linux-5.15.39-3-pve-x86_64-with-glibc2.35", + "platform_version": "#2 SMP PVE 5.15.39-3 (Wed, 27 Jul 2022 13:45:39 +0200)", + "python_version": "3.10.5 (main, Jul 30 2022, 06:09:26) [GCC 9.4.0]" + }, + "spdx_license_list_version": "3.17", + "files_count": 6 + } + } + ], + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 6, + "dirs_count": 0, + "size_count": 4925, + "scan_errors": [] + }, + { + "path": "package/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 1764, + "date": "2014-04-17", + "sha1": "b75c6b10bbfac1092ef493079ae044cc89824dc0", + "md5": "7a15b8fe67321134796aa0efe08015d0", + "sha256": "17c7c4c5ba278eacdf05f8e62243edda7036c39f4b61448aa753c77b078a11ed", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/CONTRIBUTING.md", + "type": "file", + "name": "CONTRIBUTING.md", + "base_name": "CONTRIBUTING", + "extension": ".md", + "size": 123, + "date": "2013-02-12", + "sha1": "12ce7abccdd3aeebd1d093a30bb1768f120fb8cb", + "md5": "390ff2e45c4ab33b721d10a45c147c0c", + "sha256": "a654f59a0ffa0d17296a230c512790f66144a18e410222cab9bfe3a00fd8b7e8", + "mime_type": "text/html", + "file_type": "HTML document, ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 1092, + "date": "2011-05-13", + "sha1": "4a1927e74796f06ac7e7a687ca6b44d39c65d8f4", + "md5": "b3245a33f2d41818f14c489bd33bc4a8", + "sha256": "ab95d69fc43021e0451b5021ff09fbc89ee6f9199354397f18fe7ce3f4b50554", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 378, + "date": "2014-04-17", + "sha1": "bb9785300f276c536e35e4050aa06e93fed3e0aa", + "md5": "cb4d65ccc150047accc4feab2875697f", + "sha256": "cd8af8b13b7f5f0072c3fcbef24a341fd314e667a8d3994d22553df14b1fb5f5", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2010-10-04", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/test.js", + "type": "file", + "name": "test.js", + "base_name": "test", + "extension": ".js", + "size": 1069, + "date": "2014-04-17", + "sha1": "69ec8e64451d6fbcc2d53717c5f3a630bd0432d8", + "md5": "245853103cf43465e31ac1b255edbfed", + "sha256": "34a78de842db494b0ff8ad4574a93cbbc4bf6e74cb064ab0f6bab6d11b80833a", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.6-i-expected.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.6-i-expected.json new file mode 100644 index 00000000..34255690 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.6-i-expected.json @@ -0,0 +1,200 @@ +{ + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.6" + ], + "files_count": 6, + "dirs_count": 0, + "size_count": 4511, + "scan_errors": [] + }, + { + "path": "package/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 1764, + "date": "2014-04-17", + "sha1": "b75c6b10bbfac1092ef493079ae044cc89824dc0", + "md5": "7a15b8fe67321134796aa0efe08015d0", + "sha256": "17c7c4c5ba278eacdf05f8e62243edda7036c39f4b61448aa753c77b078a11ed", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.6" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/CONTRIBUTING.md", + "type": "file", + "name": "CONTRIBUTING.md", + "base_name": "CONTRIBUTING", + "extension": ".md", + "size": 123, + "date": "2013-02-12", + "sha1": "12ce7abccdd3aeebd1d093a30bb1768f120fb8cb", + "md5": "390ff2e45c4ab33b721d10a45c147c0c", + "sha256": "a654f59a0ffa0d17296a230c512790f66144a18e410222cab9bfe3a00fd8b7e8", + "mime_type": "text/html", + "file_type": "HTML document, ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.6" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 765, + "date": "2015-05-21", + "sha1": "bb408e929caeb1731945b2ba54bc337edb87cc66", + "md5": "82703a69f6d7411dde679954c2fd9dca", + "sha256": "4ec3d4c66cd87f5c8d8ad911b10f99bf27cb00cdfcff82621956e379186b016b", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.6" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 291, + "date": "2015-05-21", + "sha1": "e91829a4c5cbb009c7c36f3309c6b3471f216200", + "md5": "fc8be3703299c41fd19312cfd1996bea", + "sha256": "eec1ecc446cfe64288192e5a6ae6accc2c9d207085833ed69d951cbd1ce9a0ca", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.6" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2010-10-04", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.6" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/test.js", + "type": "file", + "name": "test.js", + "base_name": "test", + "extension": ".js", + "size": 1069, + "date": "2014-04-17", + "sha1": "69ec8e64451d6fbcc2d53717c5f3a630bd0432d8", + "md5": "245853103cf43465e31ac1b255edbfed", + "sha256": "34a78de842db494b0ff8ad4574a93cbbc4bf6e74cb064ab0f6bab6d11b80833a", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.6" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.6-i.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.6-i.json new file mode 100644 index 00000000..ded28889 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.6-i.json @@ -0,0 +1,211 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "31.2.2", + "options": { + "input": [ + "package" + ], + "--info": true, + "--json-pp": "./abbrev-1.0.6.tgz-i.json" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2022-12-02T011504.876898", + "end_timestamp": "2022-12-02T011505.035730", + "output_format_version": "2.0.0", + "duration": 0.15884780883789062, + "message": null, + "errors": [], + "warnings": [], + "extra_data": { + "system_environment": { + "operating_system": "linux", + "cpu_architecture": "64", + "platform": "Linux-5.15.39-3-pve-x86_64-with-glibc2.35", + "platform_version": "#2 SMP PVE 5.15.39-3 (Wed, 27 Jul 2022 13:45:39 +0200)", + "python_version": "3.10.5 (main, Jul 30 2022, 06:09:26) [GCC 9.4.0]" + }, + "spdx_license_list_version": "3.17", + "files_count": 6 + } + } + ], + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 6, + "dirs_count": 0, + "size_count": 4511, + "scan_errors": [] + }, + { + "path": "package/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 1764, + "date": "2014-04-17", + "sha1": "b75c6b10bbfac1092ef493079ae044cc89824dc0", + "md5": "7a15b8fe67321134796aa0efe08015d0", + "sha256": "17c7c4c5ba278eacdf05f8e62243edda7036c39f4b61448aa753c77b078a11ed", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/CONTRIBUTING.md", + "type": "file", + "name": "CONTRIBUTING.md", + "base_name": "CONTRIBUTING", + "extension": ".md", + "size": 123, + "date": "2013-02-12", + "sha1": "12ce7abccdd3aeebd1d093a30bb1768f120fb8cb", + "md5": "390ff2e45c4ab33b721d10a45c147c0c", + "sha256": "a654f59a0ffa0d17296a230c512790f66144a18e410222cab9bfe3a00fd8b7e8", + "mime_type": "text/html", + "file_type": "HTML document, ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 765, + "date": "2015-05-21", + "sha1": "bb408e929caeb1731945b2ba54bc337edb87cc66", + "md5": "82703a69f6d7411dde679954c2fd9dca", + "sha256": "4ec3d4c66cd87f5c8d8ad911b10f99bf27cb00cdfcff82621956e379186b016b", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 291, + "date": "2015-05-21", + "sha1": "e91829a4c5cbb009c7c36f3309c6b3471f216200", + "md5": "fc8be3703299c41fd19312cfd1996bea", + "sha256": "eec1ecc446cfe64288192e5a6ae6accc2c9d207085833ed69d951cbd1ce9a0ca", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2010-10-04", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/test.js", + "type": "file", + "name": "test.js", + "base_name": "test", + "extension": ".js", + "size": 1069, + "date": "2014-04-17", + "sha1": "69ec8e64451d6fbcc2d53717c5f3a630bd0432d8", + "md5": "245853103cf43465e31ac1b255edbfed", + "sha256": "34a78de842db494b0ff8ad4574a93cbbc4bf6e74cb064ab0f6bab6d11b80833a", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.7-i-expected.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.7-i-expected.json new file mode 100644 index 00000000..12fdd2a0 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.7-i-expected.json @@ -0,0 +1,256 @@ +{ + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.7" + ], + "files_count": 8, + "dirs_count": 0, + "size_count": 4669, + "scan_errors": [] + }, + { + "path": "package/.npmignore", + "type": "file", + "name": ".npmignore", + "base_name": ".npmignore", + "extension": "", + "size": 45, + "date": "2015-05-30", + "sha1": "07cd07890babc5d03fd44e1ce9203b4f16c848e1", + "md5": "55fa175c873c423673dd58fae4232e9c", + "sha256": "7048571a371f162971b2d0890f86fd8e6d25f316c40f918f7149dbf3bc10aca8", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "GAS", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.7" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/.travis.yml", + "type": "file", + "name": ".travis.yml", + "base_name": ".travis", + "extension": ".yml", + "size": 60, + "date": "2015-05-30", + "sha1": "79213a95749ddb02aac8146f88c2e9ee705bec84", + "md5": "6b832410f2ba5cc9b136ee1e7fbaf1f1", + "sha256": "c6b8a08f01178dfb859bccdd8be0a71b00d494587d5cf1d60188ccb036ca2030", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.7" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 1764, + "date": "2014-04-17", + "sha1": "b75c6b10bbfac1092ef493079ae044cc89824dc0", + "md5": "7a15b8fe67321134796aa0efe08015d0", + "sha256": "17c7c4c5ba278eacdf05f8e62243edda7036c39f4b61448aa753c77b078a11ed", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.7" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/CONTRIBUTING.md", + "type": "file", + "name": "CONTRIBUTING.md", + "base_name": "CONTRIBUTING", + "extension": ".md", + "size": 123, + "date": "2013-02-12", + "sha1": "12ce7abccdd3aeebd1d093a30bb1768f120fb8cb", + "md5": "390ff2e45c4ab33b721d10a45c147c0c", + "sha256": "a654f59a0ffa0d17296a230c512790f66144a18e410222cab9bfe3a00fd8b7e8", + "mime_type": "text/html", + "file_type": "HTML document, ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.7" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 765, + "date": "2015-05-21", + "sha1": "bb408e929caeb1731945b2ba54bc337edb87cc66", + "md5": "82703a69f6d7411dde679954c2fd9dca", + "sha256": "4ec3d4c66cd87f5c8d8ad911b10f99bf27cb00cdfcff82621956e379186b016b", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.7" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 344, + "date": "2015-05-30", + "sha1": "bf635dc9247c69abda92a42242524e74fd5eeec1", + "md5": "ac9892e616f40a638a2d93995ee1d506", + "sha256": "0b089a246155f2986ee01b6e4a67ab637139d7ce49a6790bf76c0a4fb10e824f", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.7" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2010-10-04", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.7" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/test.js", + "type": "file", + "name": "test.js", + "base_name": "test", + "extension": ".js", + "size": 1069, + "date": "2015-05-30", + "sha1": "4af3fea0290e02e4d82225ce75721b423c212483", + "md5": "f74996e9aaee131e03836bcb7853e248", + "sha256": "4ec8b1e563a76ec884ef9a13947c52b9dc528eab760ee89379924b23772f347d", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.7" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.7-i.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.7-i.json new file mode 100644 index 00000000..c21d27e7 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.7-i.json @@ -0,0 +1,261 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "31.2.2", + "options": { + "input": [ + "package" + ], + "--info": true, + "--json-pp": "./abbrev-1.0.7.tgz-i.json" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2022-12-02T011506.139876", + "end_timestamp": "2022-12-02T011506.316847", + "output_format_version": "2.0.0", + "duration": 0.1769883632659912, + "message": null, + "errors": [], + "warnings": [], + "extra_data": { + "system_environment": { + "operating_system": "linux", + "cpu_architecture": "64", + "platform": "Linux-5.15.39-3-pve-x86_64-with-glibc2.35", + "platform_version": "#2 SMP PVE 5.15.39-3 (Wed, 27 Jul 2022 13:45:39 +0200)", + "python_version": "3.10.5 (main, Jul 30 2022, 06:09:26) [GCC 9.4.0]" + }, + "spdx_license_list_version": "3.17", + "files_count": 8 + } + } + ], + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 8, + "dirs_count": 0, + "size_count": 4669, + "scan_errors": [] + }, + { + "path": "package/.npmignore", + "type": "file", + "name": ".npmignore", + "base_name": ".npmignore", + "extension": "", + "size": 45, + "date": "2015-05-30", + "sha1": "07cd07890babc5d03fd44e1ce9203b4f16c848e1", + "md5": "55fa175c873c423673dd58fae4232e9c", + "sha256": "7048571a371f162971b2d0890f86fd8e6d25f316c40f918f7149dbf3bc10aca8", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "GAS", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/.travis.yml", + "type": "file", + "name": ".travis.yml", + "base_name": ".travis", + "extension": ".yml", + "size": 60, + "date": "2015-05-30", + "sha1": "79213a95749ddb02aac8146f88c2e9ee705bec84", + "md5": "6b832410f2ba5cc9b136ee1e7fbaf1f1", + "sha256": "c6b8a08f01178dfb859bccdd8be0a71b00d494587d5cf1d60188ccb036ca2030", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 1764, + "date": "2014-04-17", + "sha1": "b75c6b10bbfac1092ef493079ae044cc89824dc0", + "md5": "7a15b8fe67321134796aa0efe08015d0", + "sha256": "17c7c4c5ba278eacdf05f8e62243edda7036c39f4b61448aa753c77b078a11ed", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/CONTRIBUTING.md", + "type": "file", + "name": "CONTRIBUTING.md", + "base_name": "CONTRIBUTING", + "extension": ".md", + "size": 123, + "date": "2013-02-12", + "sha1": "12ce7abccdd3aeebd1d093a30bb1768f120fb8cb", + "md5": "390ff2e45c4ab33b721d10a45c147c0c", + "sha256": "a654f59a0ffa0d17296a230c512790f66144a18e410222cab9bfe3a00fd8b7e8", + "mime_type": "text/html", + "file_type": "HTML document, ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 765, + "date": "2015-05-21", + "sha1": "bb408e929caeb1731945b2ba54bc337edb87cc66", + "md5": "82703a69f6d7411dde679954c2fd9dca", + "sha256": "4ec3d4c66cd87f5c8d8ad911b10f99bf27cb00cdfcff82621956e379186b016b", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 344, + "date": "2015-05-30", + "sha1": "bf635dc9247c69abda92a42242524e74fd5eeec1", + "md5": "ac9892e616f40a638a2d93995ee1d506", + "sha256": "0b089a246155f2986ee01b6e4a67ab637139d7ce49a6790bf76c0a4fb10e824f", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2010-10-04", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/test.js", + "type": "file", + "name": "test.js", + "base_name": "test", + "extension": ".js", + "size": 1069, + "date": "2015-05-30", + "sha1": "4af3fea0290e02e4d82225ce75721b423c212483", + "md5": "f74996e9aaee131e03836bcb7853e248", + "sha256": "4ec8b1e563a76ec884ef9a13947c52b9dc528eab760ee89379924b23772f347d", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.9-i-expected.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.9-i-expected.json new file mode 100644 index 00000000..6bee67a2 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.9-i-expected.json @@ -0,0 +1,144 @@ +{ + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.9" + ], + "files_count": 4, + "dirs_count": 0, + "size_count": 3406, + "scan_errors": [] + }, + { + "path": "package/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 1764, + "date": "2014-04-17", + "sha1": "b75c6b10bbfac1092ef493079ae044cc89824dc0", + "md5": "7a15b8fe67321134796aa0efe08015d0", + "sha256": "17c7c4c5ba278eacdf05f8e62243edda7036c39f4b61448aa753c77b078a11ed", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.9" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 765, + "date": "2015-05-21", + "sha1": "bb408e929caeb1731945b2ba54bc337edb87cc66", + "md5": "82703a69f6d7411dde679954c2fd9dca", + "sha256": "4ec3d4c66cd87f5c8d8ad911b10f99bf27cb00cdfcff82621956e379186b016b", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.9" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 378, + "date": "2016-06-15", + "sha1": "ca19f68ffb963539da6fc96e125db7e4d52d1a64", + "md5": "016e057514abfcc10ee95a865fa55b30", + "sha256": "a3c7abc0591b39d43f14e2016606433f0daba56006bf2927ee07718fdf9ee6ce", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.9" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2010-10-04", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.0.9" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.9-i.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.9-i.json new file mode 100644 index 00000000..15fe6287 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.9-i.json @@ -0,0 +1,161 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "31.2.2", + "options": { + "input": [ + "package" + ], + "--info": true, + "--json-pp": "./abbrev-1.0.9.tgz-i.json" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2022-12-02T011507.424965", + "end_timestamp": "2022-12-02T011507.581639", + "output_format_version": "2.0.0", + "duration": 0.156691312789917, + "message": null, + "errors": [], + "warnings": [], + "extra_data": { + "system_environment": { + "operating_system": "linux", + "cpu_architecture": "64", + "platform": "Linux-5.15.39-3-pve-x86_64-with-glibc2.35", + "platform_version": "#2 SMP PVE 5.15.39-3 (Wed, 27 Jul 2022 13:45:39 +0200)", + "python_version": "3.10.5 (main, Jul 30 2022, 06:09:26) [GCC 9.4.0]" + }, + "spdx_license_list_version": "3.17", + "files_count": 4 + } + } + ], + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 4, + "dirs_count": 0, + "size_count": 3406, + "scan_errors": [] + }, + { + "path": "package/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 1764, + "date": "2014-04-17", + "sha1": "b75c6b10bbfac1092ef493079ae044cc89824dc0", + "md5": "7a15b8fe67321134796aa0efe08015d0", + "sha256": "17c7c4c5ba278eacdf05f8e62243edda7036c39f4b61448aa753c77b078a11ed", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 765, + "date": "2015-05-21", + "sha1": "bb408e929caeb1731945b2ba54bc337edb87cc66", + "md5": "82703a69f6d7411dde679954c2fd9dca", + "sha256": "4ec3d4c66cd87f5c8d8ad911b10f99bf27cb00cdfcff82621956e379186b016b", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 378, + "date": "2016-06-15", + "sha1": "ca19f68ffb963539da6fc96e125db7e4d52d1a64", + "md5": "016e057514abfcc10ee95a865fa55b30", + "sha256": "a3c7abc0591b39d43f14e2016606433f0daba56006bf2927ee07718fdf9ee6ce", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2010-10-04", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.0-i-expected.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.0-i-expected.json new file mode 100644 index 00000000..8648adba --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.0-i-expected.json @@ -0,0 +1,144 @@ +{ + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.1.0" + ], + "files_count": 4, + "dirs_count": 0, + "size_count": 3536, + "scan_errors": [] + }, + { + "path": "package/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 1763, + "date": "2017-02-14", + "sha1": "e33940719dacc3ea04a0fb3efd7f5a57987b6257", + "md5": "295cdcca75c99f4bc11113aca4cc9dac", + "sha256": "77e68ed8bb552a11a5ece29800e0afe34bcc098d14a1b88dd44273f68be43943", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.1.0" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 765, + "date": "2015-05-21", + "sha1": "bb408e929caeb1731945b2ba54bc337edb87cc66", + "md5": "82703a69f6d7411dde679954c2fd9dca", + "sha256": "4ec3d4c66cd87f5c8d8ad911b10f99bf27cb00cdfcff82621956e379186b016b", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.1.0" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 509, + "date": "2017-02-14", + "sha1": "3d5bc8b4159c4a42b0ebfc9031178092c62157cb", + "md5": "31d03131e6a650c48c27e3e5fb03b6a4", + "sha256": "6cb52486d339e09b8c78e4161664b19845dff6d27950def4505f32452dde0a0e", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.1.0" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2010-10-04", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.1.0" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.0-i.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.0-i.json new file mode 100644 index 00000000..44621e67 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.0-i.json @@ -0,0 +1,161 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "31.2.2", + "options": { + "input": [ + "package" + ], + "--info": true, + "--json-pp": "./abbrev-1.1.0.tgz-i.json" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2022-12-02T011508.681438", + "end_timestamp": "2022-12-02T011508.853336", + "output_format_version": "2.0.0", + "duration": 0.17191386222839355, + "message": null, + "errors": [], + "warnings": [], + "extra_data": { + "system_environment": { + "operating_system": "linux", + "cpu_architecture": "64", + "platform": "Linux-5.15.39-3-pve-x86_64-with-glibc2.35", + "platform_version": "#2 SMP PVE 5.15.39-3 (Wed, 27 Jul 2022 13:45:39 +0200)", + "python_version": "3.10.5 (main, Jul 30 2022, 06:09:26) [GCC 9.4.0]" + }, + "spdx_license_list_version": "3.17", + "files_count": 4 + } + } + ], + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 4, + "dirs_count": 0, + "size_count": 3536, + "scan_errors": [] + }, + { + "path": "package/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 1763, + "date": "2017-02-14", + "sha1": "e33940719dacc3ea04a0fb3efd7f5a57987b6257", + "md5": "295cdcca75c99f4bc11113aca4cc9dac", + "sha256": "77e68ed8bb552a11a5ece29800e0afe34bcc098d14a1b88dd44273f68be43943", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 765, + "date": "2015-05-21", + "sha1": "bb408e929caeb1731945b2ba54bc337edb87cc66", + "md5": "82703a69f6d7411dde679954c2fd9dca", + "sha256": "4ec3d4c66cd87f5c8d8ad911b10f99bf27cb00cdfcff82621956e379186b016b", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 509, + "date": "2017-02-14", + "sha1": "3d5bc8b4159c4a42b0ebfc9031178092c62157cb", + "md5": "31d03131e6a650c48c27e3e5fb03b6a4", + "sha256": "6cb52486d339e09b8c78e4161664b19845dff6d27950def4505f32452dde0a0e", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2010-10-04", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.1-i-expected.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.1-i-expected.json new file mode 100644 index 00000000..ede99667 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.1-i-expected.json @@ -0,0 +1,144 @@ +{ + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.1.1" + ], + "files_count": 4, + "dirs_count": 0, + "size_count": 4782, + "scan_errors": [] + }, + { + "path": "package/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 1763, + "date": "2017-02-14", + "sha1": "e33940719dacc3ea04a0fb3efd7f5a57987b6257", + "md5": "295cdcca75c99f4bc11113aca4cc9dac", + "sha256": "77e68ed8bb552a11a5ece29800e0afe34bcc098d14a1b88dd44273f68be43943", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.1.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 2011, + "date": "2017-09-28", + "sha1": "34d4249a8ef23970810fd3018b9399b1268dc052", + "md5": "e9c0b639498fbe60d17b10099aba77c0", + "sha256": "9e0d5c7989f7e9f07d7c4b158aceff270f235eb7464ace41c5e7b200834a43e0", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.1.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 509, + "date": "2017-09-28", + "sha1": "bfc3606e605421f81936c77f156694df03ea1f55", + "md5": "09144e5559c19012a5ad2b1cb548f188", + "sha256": "5bcbdff71c063d5177f25fd3a5c7a6c2a9d565d968765ee3a1e73449dc0bc671", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.1.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2010-10-04", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:npm/abbrev@1.1.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.1-i.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.1-i.json new file mode 100644 index 00000000..46de30fb --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.1-i.json @@ -0,0 +1,161 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "31.2.2", + "options": { + "input": [ + "package" + ], + "--info": true, + "--json-pp": "./abbrev-1.1.1.tgz-i.json" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2022-12-02T011509.963900", + "end_timestamp": "2022-12-02T011510.119020", + "output_format_version": "2.0.0", + "duration": 0.1551363468170166, + "message": null, + "errors": [], + "warnings": [], + "extra_data": { + "system_environment": { + "operating_system": "linux", + "cpu_architecture": "64", + "platform": "Linux-5.15.39-3-pve-x86_64-with-glibc2.35", + "platform_version": "#2 SMP PVE 5.15.39-3 (Wed, 27 Jul 2022 13:45:39 +0200)", + "python_version": "3.10.5 (main, Jul 30 2022, 06:09:26) [GCC 9.4.0]" + }, + "spdx_license_list_version": "3.17", + "files_count": 4 + } + } + ], + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 4, + "dirs_count": 0, + "size_count": 4782, + "scan_errors": [] + }, + { + "path": "package/abbrev.js", + "type": "file", + "name": "abbrev.js", + "base_name": "abbrev", + "extension": ".js", + "size": 1763, + "date": "2017-02-14", + "sha1": "e33940719dacc3ea04a0fb3efd7f5a57987b6257", + "md5": "295cdcca75c99f4bc11113aca4cc9dac", + "sha256": "77e68ed8bb552a11a5ece29800e0afe34bcc098d14a1b88dd44273f68be43943", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 2011, + "date": "2017-09-28", + "sha1": "34d4249a8ef23970810fd3018b9399b1268dc052", + "md5": "e9c0b639498fbe60d17b10099aba77c0", + "sha256": "9e0d5c7989f7e9f07d7c4b158aceff270f235eb7464ace41c5e7b200834a43e0", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 509, + "date": "2017-09-28", + "sha1": "bfc3606e605421f81936c77f156694df03ea1f55", + "md5": "09144e5559c19012a5ad2b1cb548f188", + "sha256": "5bcbdff71c063d5177f25fd3a5c7a6c2a9d565d968765ee3a1e73449dc0bc671", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 499, + "date": "2010-10-04", + "sha1": "c520bc857ec612ed88e13d794c47882d5aed3286", + "md5": "96b93093abdfdfef1ef8a3e2d5ca7f71", + "sha256": "2581765d44e15c58a2b88ad7bc9cc5c9ee029b4b5013c06dc45d9e94e8cb2ba4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/get-stdin-3.0.2-i-expected.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/get-stdin-3.0.2-i-expected.json new file mode 100644 index 00000000..dbfe47b0 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/get-stdin-3.0.2-i-expected.json @@ -0,0 +1,108 @@ +{ + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [], + "files_count": 3, + "dirs_count": 0, + "size_count": 1782, + "scan_errors": [] + }, + { + "path": "package/index.js", + "type": "file", + "name": "index.js", + "base_name": "index", + "extension": ".js", + "size": 584, + "date": "2014-11-23", + "sha1": "7ccb344f96ee52fe55c74b68b9353b6f298334a7", + "md5": "2c54a36c455bbca429cac637bfe67062", + "sha256": "88e96e809108df09b61c5b6cbe9583301f4948ab9187c39807c73af7b331f047", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "matched_to": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 627, + "date": "2014-11-23", + "sha1": "823909c69f1eef7b852501345601a2b7573f4e63", + "md5": "a8a5350c8c0efd5eab31a301fd8a500e", + "sha256": "da3c66ea4c17a9122c246b769fb8370e963bb19a8e334cc486a70ae40ccb6c80", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/readme.md", + "type": "file", + "name": "readme.md", + "base_name": "readme", + "extension": ".md", + "size": 571, + "date": "2014-08-17", + "sha1": "463253693f496ad1b7e3d7504489d24625a33859", + "md5": "66e9c35acf0e22691b69f94f59f99edb", + "sha256": "337197b8f15bbe4c358129613d4de52fa1ef94b1613fa9292c0cef9c167595ce", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/get-stdin-3.0.2-i.json b/matchcode/tests/matchcode/testfiles/match/directory-matching/get-stdin-3.0.2-i.json new file mode 100644 index 00000000..dce41217 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/directory-matching/get-stdin-3.0.2-i.json @@ -0,0 +1,136 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "31.2.2", + "options": { + "input": [ + "package/" + ], + "--info": true, + "--json-pp": "get-stdin-3.0.2-i.json" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2022-12-02T012403.760550", + "end_timestamp": "2022-12-02T012403.875478", + "output_format_version": "2.0.0", + "duration": 0.1149442195892334, + "message": null, + "errors": [], + "warnings": [], + "extra_data": { + "system_environment": { + "operating_system": "linux", + "cpu_architecture": "64", + "platform": "Linux-5.15.39-3-pve-x86_64-with-glibc2.35", + "platform_version": "#2 SMP PVE 5.15.39-3 (Wed, 27 Jul 2022 13:45:39 +0200)", + "python_version": "3.10.5 (main, Jul 30 2022, 06:09:26) [GCC 9.4.0]" + }, + "spdx_license_list_version": "3.17", + "files_count": 3 + } + } + ], + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 3, + "dirs_count": 0, + "size_count": 1782, + "scan_errors": [] + }, + { + "path": "package/index.js", + "type": "file", + "name": "index.js", + "base_name": "index", + "extension": ".js", + "size": 584, + "date": "2014-11-23", + "sha1": "7ccb344f96ee52fe55c74b68b9353b6f298334a7", + "md5": "2c54a36c455bbca429cac637bfe67062", + "sha256": "88e96e809108df09b61c5b6cbe9583301f4948ab9187c39807c73af7b331f047", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 627, + "date": "2014-11-23", + "sha1": "823909c69f1eef7b852501345601a2b7573f4e63", + "md5": "a8a5350c8c0efd5eab31a301fd8a500e", + "sha256": "da3c66ea4c17a9122c246b769fb8370e963bb19a8e334cc486a70ae40ccb6c80", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/readme.md", + "type": "file", + "name": "readme.md", + "base_name": "readme", + "extension": ".md", + "size": 571, + "date": "2014-08-17", + "sha1": "463253693f496ad1b7e3d7504489d24625a33859", + "md5": "66e9c35acf0e22691b69f94f59f99edb", + "sha256": "337197b8f15bbe4c358129613d4de52fa1ef94b1613fa9292c0cef9c167595ce", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/nested/nested-expected.json b/matchcode/tests/matchcode/testfiles/match/nested/nested-expected.json new file mode 100644 index 00000000..37868940 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/nested/nested-expected.json @@ -0,0 +1,650 @@ +{ + "files": [ + { + "path": "nested", + "type": "directory", + "name": "nested", + "base_name": "nested", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "matched_to": [], + "files_count": 11, + "dirs_count": 5, + "size_count": 244503, + "scan_errors": [] + }, + { + "path": "nested/underscore", + "type": "directory", + "name": "underscore", + "base_name": "underscore", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "matched_to": [], + "files_count": 11, + "dirs_count": 4, + "size_count": 244503, + "scan_errors": [] + }, + { + "path": "nested/underscore/index.d.ts", + "type": "file", + "name": "index.d.ts", + "base_name": "index.d", + "extension": ".ts", + "size": 212101, + "date": "2020-07-15", + "sha1": "4f2b85857c3a162c5d536e342ba417fa6c03d40a", + "md5": "ae0acb15531b2efe7253a9bd1fee1b86", + "sha256": "92c3d4b6cf13af286895b484b680b314be34a62a41d1440a2d62d5d5cf0e93b3", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "matched_to": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 1141, + "date": "2020-07-15", + "sha1": "689ec0681815ecc32bee639c68e7740add7bd301", + "md5": "d4a904ca135bb7bc912156fee12726f0", + "sha256": "c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "matched_to": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 1940, + "date": "2020-07-15", + "sha1": "e751d5da4a71b9eaec5114e8a5b5eceef15d0b4d", + "md5": "a8b2dc907f99ba2e34a97954075d9b8f", + "sha256": "31e806866fe2800471fd3544fc17ed77f9b58885ff8f0590d3dc8d623b897206", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [ + { + "type": "npm", + "namespace": "@types", + "name": "underscore", + "version": "1.10.9", + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": "TypeScript definitions for Underscore", + "release_date": null, + "parties": [ + { + "type": "person", + "role": "contributor", + "name": "Boris Yankov", + "email": null, + "url": "https://github.com/borisyankov" + }, + { + "type": "person", + "role": "contributor", + "name": "Josh Baldwin", + "email": null, + "url": "https://github.com/jbaldwin" + }, + { + "type": "person", + "role": "contributor", + "name": "Christopher Currens", + "email": null, + "url": "https://github.com/ccurrens" + }, + { + "type": "person", + "role": "contributor", + "name": "Ard Timmerman", + "email": null, + "url": "https://github.com/confususs" + }, + { + "type": "person", + "role": "contributor", + "name": "Julian Gonggrijp", + "email": null, + "url": "https://github.com/jgonggrijp" + }, + { + "type": "person", + "role": "contributor", + "name": "Florian Keller", + "email": null, + "url": "https://github.com/ffflorian" + }, + { + "type": "person", + "role": "contributor", + "name": "Regev Brody", + "email": null, + "url": "https://github.com/regevbr" + }, + { + "type": "person", + "role": "contributor", + "name": "Piotr B\u0142a\u017cejewicz", + "email": null, + "url": "https://github.com/peterblazejewicz" + }, + { + "type": "person", + "role": "contributor", + "name": "Michael Ness", + "email": null, + "url": "https://github.com/reubenrybnik" + } + ], + "keywords": [], + "homepage_url": null, + "download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": "git+https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "copyright": null, + "license_expression": "mit", + "declared_license": [ + "MIT" + ], + "notice_text": null, + "root_path": "nested/underscore", + "dependencies": [], + "contains_source_code": null, + "source_packages": [], + "purl": "pkg:npm/%40types/underscore@1.10.9", + "repository_homepage_url": "https://www.npmjs.com/package/@types/underscore", + "repository_download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "api_data_url": "https://registry.npmjs.org/@types%2funderscore" + } + ], + "matched_to": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 889, + "date": "2020-07-15", + "sha1": "0a9a39ef14ee88de5e8c4ed6aaeef3ee64f9421f", + "md5": "c8223d72fe3e5f79ac77f9566e7e8e28", + "sha256": "abd4f29b3bf6e302720577ad706f15f68fa13ff227aeeb9e1b091d66e54bc5d9", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines, with CRLF line terminators", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "matched_to": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules", + "type": "directory", + "name": "node_modules", + "base_name": "node_modules", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "matched_to": [], + "files_count": 7, + "dirs_count": 3, + "size_count": 28432, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 7, + "dirs_count": 2, + "size_count": 28432, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/CHANGELOG.md", + "type": "file", + "name": "CHANGELOG.md", + "base_name": "CHANGELOG", + "extension": ".md", + "size": 3309, + "date": "1985-10-26", + "sha1": "79625a8ec840826bbab4e6658c53c11ea950fcd3", + "md5": "37600475d22e98ae0e8e1e606e2991e7", + "sha256": "33c8629e8ddd3817357bf31d68b76379cbea6f7588b63df152b0d57f7fdd393b", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 771, + "date": "1985-10-26", + "sha1": "33f98f5756ed1098e7242883aaef2b74dfa06e8d", + "md5": "998d31468d34220e7daf05d41196767c", + "sha256": "15fa5153db3daa38c6bc1d5c5cb73d34064c1c0426cfccb91ea62735a95aedaa", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [ + { + "type": "npm", + "namespace": "@umijs", + "name": "plugin-request", + "version": "2.4.1", + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": "@umijs/plugin-request", + "release_date": null, + "parties": [], + "keywords": [ + "umi" + ], + "homepage_url": "https://github.com/umijs/plugins/tree/master/packages/plugin-request#readme", + "download_url": "https://registry.npmjs.org/@umijs/plugin-request/-/plugin-request-2.4.1.tgz", + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": "http://github.com/umijs/plugins/issues", + "code_view_url": null, + "vcs_url": "git+https://github.com/umijs/plugins", + "copyright": null, + "license_expression": "mit", + "declared_license": [ + "MIT" + ], + "notice_text": null, + "root_path": "nested/underscore/node_modules/package", + "dependencies": [ + { + "purl": "pkg:npm/%40ahooksjs/use-request", + "requirement": "^2.0.0", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false + }, + { + "purl": "pkg:npm/umi-request", + "requirement": "^1.2.14", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false + }, + { + "purl": "pkg:npm/umi", + "requirement": "3.x", + "scope": "peerDependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false + } + ], + "contains_source_code": null, + "source_packages": [], + "purl": "pkg:npm/%40umijs/plugin-request@2.4.1", + "repository_homepage_url": "https://www.npmjs.com/package/@umijs/plugin-request", + "repository_download_url": "https://registry.npmjs.org/@umijs/plugin-request/-/plugin-request-2.4.1.tgz", + "api_data_url": "https://registry.npmjs.org/@umijs%2fplugin-request" + } + ], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 307, + "date": "1985-10-26", + "sha1": "fac8486d1aef8658ae89d37c70b0edb332262109", + "md5": "a3cb10e52239977c38a699ef518dc8d4", + "sha256": "886cd0e0fafc00de3f8726a27920e449f09767dbf19aa14b4e65dc1bbdb168c7", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/lib", + "type": "directory", + "name": "lib", + "base_name": "lib", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 2, + "dirs_count": 0, + "size_count": 13964, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/lib/index.js", + "type": "file", + "name": "index.js", + "base_name": "index", + "extension": ".js", + "size": 2453, + "date": "1985-10-26", + "sha1": "aecb2790e91e0eb8f1f8608c4ac7a689a9a28467", + "md5": "0a4e5a9b46acd86352121a86f2ad6ca0", + "sha256": "8f3e85de5fec26a6c76b17170fa3671f2f0ff05efe7751339ca41a2f90b9a1b0", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text, with very long lines", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/lib/request.js", + "type": "file", + "name": "request.js", + "base_name": "request", + "extension": ".js", + "size": 11511, + "date": "1985-10-26", + "sha1": "e59e280693fc62d2a7e902475e5e13d3bf62d476", + "md5": "e00da7bc6c7649c392bc9e20cd779167", + "sha256": "9ae43cf04cc4babc3d2d6dd54f6584da5c2749018e64d396d1e06181d3c4b276", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/src", + "type": "directory", + "name": "src", + "base_name": "src", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 2, + "dirs_count": 0, + "size_count": 10081, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/src/index.ts", + "type": "file", + "name": "index.ts", + "base_name": "index", + "extension": ".ts", + "size": 2138, + "date": "1985-10-26", + "sha1": "00216c5fd157602a28b45530c6badb388405820d", + "md5": "3fa996e7e53a7447bd5e8d3455ac29b6", + "sha256": "d1d94851002539609e3b20a36594d25f10a6a8d9bd42d2e7c6b3d4f243ab52e4", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/src/request.ts", + "type": "file", + "name": "request.ts", + "base_name": "request", + "extension": ".ts", + "size": 7943, + "date": "1985-10-26", + "sha1": "c6b37045e7919219efffd83894df441e47c87cfb", + "md5": "7a77806ca3e1452cc7eb5d01a35f6264", + "sha256": "25bbbc1924dcb7a726142be254d12ace5379fbee9bf37e20a7ce300bb01030cb", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/nested/nested.json b/matchcode/tests/matchcode/testfiles/match/nested/nested.json new file mode 100644 index 00000000..c783810c --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/nested/nested.json @@ -0,0 +1,637 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "3.2.2rc3", + "options": { + "input": [ + "/tmp/test/nested/" + ], + "--info": true, + "--json-pp": "/tmp/test/nested.json", + "--package": true, + "--processes": "2" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2020-11-07T201514.714422", + "end_timestamp": "2020-11-07T201519.082743", + "duration": 4.36833381652832, + "message": null, + "errors": [], + "extra_data": { + "files_count": 11 + } + } + ], + "files": [ + { + "path": "nested", + "type": "directory", + "name": "nested", + "base_name": "nested", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 11, + "dirs_count": 5, + "size_count": 244503, + "scan_errors": [] + }, + { + "path": "nested/underscore", + "type": "directory", + "name": "underscore", + "base_name": "underscore", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 11, + "dirs_count": 4, + "size_count": 244503, + "scan_errors": [] + }, + { + "path": "nested/underscore/index.d.ts", + "type": "file", + "name": "index.d.ts", + "base_name": "index.d", + "extension": ".ts", + "size": 212101, + "date": "2020-07-15", + "sha1": "4f2b85857c3a162c5d536e342ba417fa6c03d40a", + "md5": "ae0acb15531b2efe7253a9bd1fee1b86", + "sha256": "92c3d4b6cf13af286895b484b680b314be34a62a41d1440a2d62d5d5cf0e93b3", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 1141, + "date": "2020-07-15", + "sha1": "689ec0681815ecc32bee639c68e7740add7bd301", + "md5": "d4a904ca135bb7bc912156fee12726f0", + "sha256": "c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 1940, + "date": "2020-07-15", + "sha1": "e751d5da4a71b9eaec5114e8a5b5eceef15d0b4d", + "md5": "a8b2dc907f99ba2e34a97954075d9b8f", + "sha256": "31e806866fe2800471fd3544fc17ed77f9b58885ff8f0590d3dc8d623b897206", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [ + { + "type": "npm", + "namespace": "@types", + "name": "underscore", + "version": "1.10.9", + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": "TypeScript definitions for Underscore", + "release_date": null, + "parties": [ + { + "type": "person", + "role": "contributor", + "name": "Boris Yankov", + "email": null, + "url": "https://github.com/borisyankov" + }, + { + "type": "person", + "role": "contributor", + "name": "Josh Baldwin", + "email": null, + "url": "https://github.com/jbaldwin" + }, + { + "type": "person", + "role": "contributor", + "name": "Christopher Currens", + "email": null, + "url": "https://github.com/ccurrens" + }, + { + "type": "person", + "role": "contributor", + "name": "Ard Timmerman", + "email": null, + "url": "https://github.com/confususs" + }, + { + "type": "person", + "role": "contributor", + "name": "Julian Gonggrijp", + "email": null, + "url": "https://github.com/jgonggrijp" + }, + { + "type": "person", + "role": "contributor", + "name": "Florian Keller", + "email": null, + "url": "https://github.com/ffflorian" + }, + { + "type": "person", + "role": "contributor", + "name": "Regev Brody", + "email": null, + "url": "https://github.com/regevbr" + }, + { + "type": "person", + "role": "contributor", + "name": "Piotr B\u0142a\u017cejewicz", + "email": null, + "url": "https://github.com/peterblazejewicz" + }, + { + "type": "person", + "role": "contributor", + "name": "Michael Ness", + "email": null, + "url": "https://github.com/reubenrybnik" + } + ], + "keywords": [], + "homepage_url": null, + "download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": "git+https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "copyright": null, + "license_expression": "mit", + "declared_license": [ + "MIT" + ], + "notice_text": null, + "root_path": "nested/underscore", + "dependencies": [], + "contains_source_code": null, + "source_packages": [], + "purl": "pkg:npm/%40types/underscore@1.10.9", + "repository_homepage_url": "https://www.npmjs.com/package/@types/underscore", + "repository_download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "api_data_url": "https://registry.npmjs.org/@types%2funderscore" + } + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 889, + "date": "2020-07-15", + "sha1": "0a9a39ef14ee88de5e8c4ed6aaeef3ee64f9421f", + "md5": "c8223d72fe3e5f79ac77f9566e7e8e28", + "sha256": "abd4f29b3bf6e302720577ad706f15f68fa13ff227aeeb9e1b091d66e54bc5d9", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines, with CRLF line terminators", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules", + "type": "directory", + "name": "node_modules", + "base_name": "node_modules", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 7, + "dirs_count": 3, + "size_count": 28432, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 7, + "dirs_count": 2, + "size_count": 28432, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/CHANGELOG.md", + "type": "file", + "name": "CHANGELOG.md", + "base_name": "CHANGELOG", + "extension": ".md", + "size": 3309, + "date": "1985-10-26", + "sha1": "79625a8ec840826bbab4e6658c53c11ea950fcd3", + "md5": "37600475d22e98ae0e8e1e606e2991e7", + "sha256": "33c8629e8ddd3817357bf31d68b76379cbea6f7588b63df152b0d57f7fdd393b", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 771, + "date": "1985-10-26", + "sha1": "33f98f5756ed1098e7242883aaef2b74dfa06e8d", + "md5": "998d31468d34220e7daf05d41196767c", + "sha256": "15fa5153db3daa38c6bc1d5c5cb73d34064c1c0426cfccb91ea62735a95aedaa", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [ + { + "type": "npm", + "namespace": "@umijs", + "name": "plugin-request", + "version": "2.4.1", + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": "@umijs/plugin-request", + "release_date": null, + "parties": [], + "keywords": [ + "umi" + ], + "homepage_url": "https://github.com/umijs/plugins/tree/master/packages/plugin-request#readme", + "download_url": "https://registry.npmjs.org/@umijs/plugin-request/-/plugin-request-2.4.1.tgz", + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": "http://github.com/umijs/plugins/issues", + "code_view_url": null, + "vcs_url": "git+https://github.com/umijs/plugins", + "copyright": null, + "license_expression": "mit", + "declared_license": [ + "MIT" + ], + "notice_text": null, + "root_path": "nested/underscore/node_modules/package", + "dependencies": [ + { + "purl": "pkg:npm/%40ahooksjs/use-request", + "requirement": "^2.0.0", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false + }, + { + "purl": "pkg:npm/umi-request", + "requirement": "^1.2.14", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false + }, + { + "purl": "pkg:npm/umi", + "requirement": "3.x", + "scope": "peerDependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false + } + ], + "contains_source_code": null, + "source_packages": [], + "purl": "pkg:npm/%40umijs/plugin-request@2.4.1", + "repository_homepage_url": "https://www.npmjs.com/package/@umijs/plugin-request", + "repository_download_url": "https://registry.npmjs.org/@umijs/plugin-request/-/plugin-request-2.4.1.tgz", + "api_data_url": "https://registry.npmjs.org/@umijs%2fplugin-request" + } + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 307, + "date": "1985-10-26", + "sha1": "fac8486d1aef8658ae89d37c70b0edb332262109", + "md5": "a3cb10e52239977c38a699ef518dc8d4", + "sha256": "886cd0e0fafc00de3f8726a27920e449f09767dbf19aa14b4e65dc1bbdb168c7", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/lib", + "type": "directory", + "name": "lib", + "base_name": "lib", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 2, + "dirs_count": 0, + "size_count": 13964, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/lib/index.js", + "type": "file", + "name": "index.js", + "base_name": "index", + "extension": ".js", + "size": 2453, + "date": "1985-10-26", + "sha1": "aecb2790e91e0eb8f1f8608c4ac7a689a9a28467", + "md5": "0a4e5a9b46acd86352121a86f2ad6ca0", + "sha256": "8f3e85de5fec26a6c76b17170fa3671f2f0ff05efe7751339ca41a2f90b9a1b0", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text, with very long lines", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/lib/request.js", + "type": "file", + "name": "request.js", + "base_name": "request", + "extension": ".js", + "size": 11511, + "date": "1985-10-26", + "sha1": "e59e280693fc62d2a7e902475e5e13d3bf62d476", + "md5": "e00da7bc6c7649c392bc9e20cd779167", + "sha256": "9ae43cf04cc4babc3d2d6dd54f6584da5c2749018e64d396d1e06181d3c4b276", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/src", + "type": "directory", + "name": "src", + "base_name": "src", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 2, + "dirs_count": 0, + "size_count": 10081, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/src/index.ts", + "type": "file", + "name": "index.ts", + "base_name": "index", + "extension": ".ts", + "size": 2138, + "date": "1985-10-26", + "sha1": "00216c5fd157602a28b45530c6badb388405820d", + "md5": "3fa996e7e53a7447bd5e8d3455ac29b6", + "sha256": "d1d94851002539609e3b20a36594d25f10a6a8d9bd42d2e7c6b3d4f243ab52e4", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "nested/underscore/node_modules/package/src/request.ts", + "type": "file", + "name": "request.ts", + "base_name": "request", + "extension": ".ts", + "size": 7943, + "date": "1985-10-26", + "sha1": "c6b37045e7919219efffd83894df441e47c87cfb", + "md5": "7a77806ca3e1452cc7eb5d01a35f6264", + "sha256": "25bbbc1924dcb7a726142be254d12ace5379fbee9bf37e20a7ce300bb01030cb", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/nested/new-nested-expected.json b/matchcode/tests/matchcode/testfiles/match/nested/new-nested-expected.json new file mode 100644 index 00000000..260f5581 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/nested/new-nested-expected.json @@ -0,0 +1,691 @@ +{ + "files": [ + { + "path": "underscore", + "type": "directory", + "name": "underscore", + "base_name": "underscore", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [], + "packages": [], + "matched_to": [], + "files_count": 11, + "dirs_count": 4, + "size_count": 244503, + "scan_errors": [] + }, + { + "path": "underscore/index.d.ts", + "type": "file", + "name": "index.d.ts", + "base_name": "index.d", + "extension": ".ts", + "size": 212101, + "date": "2020-07-15", + "sha1": "4f2b85857c3a162c5d536e342ba417fa6c03d40a", + "md5": "ae0acb15531b2efe7253a9bd1fee1b86", + "sha256": "92c3d4b6cf13af286895b484b680b314be34a62a41d1440a2d62d5d5cf0e93b3", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40types/underscore@1.10.9?uuid=420db78a-625f-4622-b1a0-93d1ea853194" + ], + "packages": [], + "matched_to": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 1141, + "date": "2020-07-15", + "sha1": "689ec0681815ecc32bee639c68e7740add7bd301", + "md5": "d4a904ca135bb7bc912156fee12726f0", + "sha256": "c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40types/underscore@1.10.9?uuid=420db78a-625f-4622-b1a0-93d1ea853194" + ], + "packages": [], + "matched_to": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 1940, + "date": "2020-07-15", + "sha1": "e751d5da4a71b9eaec5114e8a5b5eceef15d0b4d", + "md5": "a8b2dc907f99ba2e34a97954075d9b8f", + "sha256": "31e806866fe2800471fd3544fc17ed77f9b58885ff8f0590d3dc8d623b897206", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [ + { + "type": "npm", + "namespace": "@types", + "name": "underscore", + "version": "1.10.9", + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": "TypeScript definitions for Underscore", + "release_date": null, + "parties": [ + { + "type": "person", + "role": "contributor", + "name": "Boris Yankov", + "email": null, + "url": "https://github.com/borisyankov" + }, + { + "type": "person", + "role": "contributor", + "name": "Josh Baldwin", + "email": null, + "url": "https://github.com/jbaldwin" + }, + { + "type": "person", + "role": "contributor", + "name": "Christopher Currens", + "email": null, + "url": "https://github.com/ccurrens" + }, + { + "type": "person", + "role": "contributor", + "name": "Ard Timmerman", + "email": null, + "url": "https://github.com/confususs" + }, + { + "type": "person", + "role": "contributor", + "name": "Julian Gonggrijp", + "email": null, + "url": "https://github.com/jgonggrijp" + }, + { + "type": "person", + "role": "contributor", + "name": "Florian Keller", + "email": null, + "url": "https://github.com/ffflorian" + }, + { + "type": "person", + "role": "contributor", + "name": "Regev Brody", + "email": null, + "url": "https://github.com/regevbr" + }, + { + "type": "person", + "role": "contributor", + "name": "Piotr B\u0142a\u017cejewicz", + "email": null, + "url": "https://github.com/peterblazejewicz" + }, + { + "type": "person", + "role": "contributor", + "name": "Michael Ness", + "email": null, + "url": "https://github.com/reubenrybnik" + } + ], + "keywords": [], + "homepage_url": null, + "download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": "git+https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "copyright": null, + "license_expression": "mit", + "declared_license": [ + "MIT" + ], + "notice_text": null, + "source_packages": [], + "file_references": [], + "extra_data": {}, + "dependencies": [], + "repository_homepage_url": "https://www.npmjs.com/package/@types/underscore", + "repository_download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "api_data_url": "https://registry.npmjs.org/@types%2funderscore", + "datasource_id": "npm_package_json", + "purl": "pkg:npm/%40types/underscore@1.10.9" + } + ], + "for_packages": [ + "pkg:npm/%40types/underscore@1.10.9?uuid=420db78a-625f-4622-b1a0-93d1ea853194" + ], + "packages": [], + "matched_to": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 889, + "date": "2020-07-15", + "sha1": "0a9a39ef14ee88de5e8c4ed6aaeef3ee64f9421f", + "md5": "c8223d72fe3e5f79ac77f9566e7e8e28", + "sha256": "abd4f29b3bf6e302720577ad706f15f68fa13ff227aeeb9e1b091d66e54bc5d9", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines, with CRLF line terminators", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40types/underscore@1.10.9?uuid=420db78a-625f-4622-b1a0-93d1ea853194" + ], + "packages": [], + "matched_to": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/node_modules", + "type": "directory", + "name": "node_modules", + "base_name": "node_modules", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [], + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 7, + "dirs_count": 3, + "size_count": 28432, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [], + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 7, + "dirs_count": 2, + "size_count": 28432, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/CHANGELOG.md", + "type": "file", + "name": "CHANGELOG.md", + "base_name": "CHANGELOG", + "extension": ".md", + "size": 3309, + "date": "1985-10-26", + "sha1": "79625a8ec840826bbab4e6658c53c11ea950fcd3", + "md5": "37600475d22e98ae0e8e1e606e2991e7", + "sha256": "33c8629e8ddd3817357bf31d68b76379cbea6f7588b63df152b0d57f7fdd393b", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 771, + "date": "1985-10-26", + "sha1": "33f98f5756ed1098e7242883aaef2b74dfa06e8d", + "md5": "998d31468d34220e7daf05d41196767c", + "sha256": "15fa5153db3daa38c6bc1d5c5cb73d34064c1c0426cfccb91ea62735a95aedaa", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [ + { + "type": "npm", + "namespace": "@umijs", + "name": "plugin-request", + "version": "2.4.1", + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": "@umijs/plugin-request", + "release_date": null, + "parties": [], + "keywords": [ + "umi" + ], + "homepage_url": "https://github.com/umijs/plugins/tree/master/packages/plugin-request#readme", + "download_url": "https://registry.npmjs.org/@umijs/plugin-request/-/plugin-request-2.4.1.tgz", + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": "http://github.com/umijs/plugins/issues", + "code_view_url": null, + "vcs_url": "git+https://github.com/umijs/plugins", + "copyright": null, + "license_expression": "mit", + "declared_license": [ + "MIT" + ], + "notice_text": null, + "source_packages": [], + "file_references": [], + "extra_data": {}, + "dependencies": [ + { + "purl": "pkg:npm/%40ahooksjs/use-request", + "extracted_requirement": "^2.0.0", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false, + "resolved_package": {}, + "extra_data": {} + }, + { + "purl": "pkg:npm/umi-request", + "extracted_requirement": "^1.2.14", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false, + "resolved_package": {}, + "extra_data": {} + }, + { + "purl": "pkg:npm/umi", + "extracted_requirement": "3.x", + "scope": "peerDependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false, + "resolved_package": {}, + "extra_data": {} + } + ], + "repository_homepage_url": "https://www.npmjs.com/package/@umijs/plugin-request", + "repository_download_url": "https://registry.npmjs.org/@umijs/plugin-request/-/plugin-request-2.4.1.tgz", + "api_data_url": "https://registry.npmjs.org/@umijs%2fplugin-request", + "datasource_id": "npm_package_json", + "purl": "pkg:npm/%40umijs/plugin-request@2.4.1" + } + ], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 307, + "date": "1985-10-26", + "sha1": "fac8486d1aef8658ae89d37c70b0edb332262109", + "md5": "a3cb10e52239977c38a699ef518dc8d4", + "sha256": "886cd0e0fafc00de3f8726a27920e449f09767dbf19aa14b4e65dc1bbdb168c7", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/lib", + "type": "directory", + "name": "lib", + "base_name": "lib", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 2, + "dirs_count": 0, + "size_count": 13964, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/lib/index.js", + "type": "file", + "name": "index.js", + "base_name": "index", + "extension": ".js", + "size": 2453, + "date": "1985-10-26", + "sha1": "aecb2790e91e0eb8f1f8608c4ac7a689a9a28467", + "md5": "0a4e5a9b46acd86352121a86f2ad6ca0", + "sha256": "8f3e85de5fec26a6c76b17170fa3671f2f0ff05efe7751339ca41a2f90b9a1b0", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text, with very long lines", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/lib/request.js", + "type": "file", + "name": "request.js", + "base_name": "request", + "extension": ".js", + "size": 11511, + "date": "1985-10-26", + "sha1": "e59e280693fc62d2a7e902475e5e13d3bf62d476", + "md5": "e00da7bc6c7649c392bc9e20cd779167", + "sha256": "9ae43cf04cc4babc3d2d6dd54f6584da5c2749018e64d396d1e06181d3c4b276", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/src", + "type": "directory", + "name": "src", + "base_name": "src", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 2, + "dirs_count": 0, + "size_count": 10081, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/src/index.ts", + "type": "file", + "name": "index.ts", + "base_name": "index", + "extension": ".ts", + "size": 2138, + "date": "1985-10-26", + "sha1": "00216c5fd157602a28b45530c6badb388405820d", + "md5": "3fa996e7e53a7447bd5e8d3455ac29b6", + "sha256": "d1d94851002539609e3b20a36594d25f10a6a8d9bd42d2e7c6b3d4f243ab52e4", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/src/request.ts", + "type": "file", + "name": "request.ts", + "base_name": "request", + "extension": ".ts", + "size": 7943, + "date": "1985-10-26", + "sha1": "c6b37045e7919219efffd83894df441e47c87cfb", + "md5": "7a77806ca3e1452cc7eb5d01a35f6264", + "sha256": "25bbbc1924dcb7a726142be254d12ace5379fbee9bf37e20a7ce300bb01030cb", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "packages": [], + "matched_to": [ + "pkg:npm/plugin-request@2.4.1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/nested/new-nested.json b/matchcode/tests/matchcode/testfiles/match/nested/new-nested.json new file mode 100644 index 00000000..64ced839 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/nested/new-nested.json @@ -0,0 +1,866 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "31.2.2", + "options": { + "input": [ + "underscore" + ], + "--info": true, + "--json-pp": "new-nested.json", + "--package": true + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2022-12-01T211637.050383", + "end_timestamp": "2022-12-01T211640.918285", + "output_format_version": "2.0.0", + "duration": 3.86792254447937, + "message": null, + "errors": [], + "warnings": [], + "extra_data": { + "system_environment": { + "operating_system": "linux", + "cpu_architecture": "64", + "platform": "Linux-5.15.39-3-pve-x86_64-with-glibc2.35", + "platform_version": "#2 SMP PVE 5.15.39-3 (Wed, 27 Jul 2022 13:45:39 +0200)", + "python_version": "3.10.5 (main, Jul 30 2022, 06:09:26) [GCC 9.4.0]" + }, + "spdx_license_list_version": "3.17", + "files_count": 11 + } + } + ], + "dependencies": [ + { + "purl": "pkg:npm/%40ahooksjs/use-request", + "extracted_requirement": "^2.0.0", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false, + "resolved_package": {}, + "extra_data": {}, + "dependency_uid": "pkg:npm/%40ahooksjs/use-request?uuid=5de47a99-bc15-4ba5-93f3-21049734572c", + "for_package_uid": "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4", + "datafile_path": "underscore/node_modules/package/package.json", + "datasource_id": "npm_package_json" + }, + { + "purl": "pkg:npm/umi-request", + "extracted_requirement": "^1.2.14", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false, + "resolved_package": {}, + "extra_data": {}, + "dependency_uid": "pkg:npm/umi-request?uuid=5dfea107-7408-4721-bdab-1b34606fb796", + "for_package_uid": "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4", + "datafile_path": "underscore/node_modules/package/package.json", + "datasource_id": "npm_package_json" + }, + { + "purl": "pkg:npm/umi", + "extracted_requirement": "3.x", + "scope": "peerDependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false, + "resolved_package": {}, + "extra_data": {}, + "dependency_uid": "pkg:npm/umi?uuid=7e759ff0-b8d0-4f4c-a3ce-225c658f5bb8", + "for_package_uid": "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4", + "datafile_path": "underscore/node_modules/package/package.json", + "datasource_id": "npm_package_json" + } + ], + "packages": [ + { + "type": "npm", + "namespace": "@types", + "name": "underscore", + "version": "1.10.9", + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": "TypeScript definitions for Underscore", + "release_date": null, + "parties": [ + { + "type": "person", + "role": "contributor", + "name": "Boris Yankov", + "email": null, + "url": "https://github.com/borisyankov" + }, + { + "type": "person", + "role": "contributor", + "name": "Josh Baldwin", + "email": null, + "url": "https://github.com/jbaldwin" + }, + { + "type": "person", + "role": "contributor", + "name": "Christopher Currens", + "email": null, + "url": "https://github.com/ccurrens" + }, + { + "type": "person", + "role": "contributor", + "name": "Ard Timmerman", + "email": null, + "url": "https://github.com/confususs" + }, + { + "type": "person", + "role": "contributor", + "name": "Julian Gonggrijp", + "email": null, + "url": "https://github.com/jgonggrijp" + }, + { + "type": "person", + "role": "contributor", + "name": "Florian Keller", + "email": null, + "url": "https://github.com/ffflorian" + }, + { + "type": "person", + "role": "contributor", + "name": "Regev Brody", + "email": null, + "url": "https://github.com/regevbr" + }, + { + "type": "person", + "role": "contributor", + "name": "Piotr B\u0142a\u017cejewicz", + "email": null, + "url": "https://github.com/peterblazejewicz" + }, + { + "type": "person", + "role": "contributor", + "name": "Michael Ness", + "email": null, + "url": "https://github.com/reubenrybnik" + } + ], + "keywords": [], + "homepage_url": null, + "download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": "git+https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "copyright": null, + "license_expression": "mit", + "declared_license": [ + "MIT" + ], + "notice_text": null, + "source_packages": [], + "extra_data": {}, + "repository_homepage_url": "https://www.npmjs.com/package/@types/underscore", + "repository_download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "api_data_url": "https://registry.npmjs.org/@types%2funderscore", + "package_uid": "pkg:npm/%40types/underscore@1.10.9?uuid=420db78a-625f-4622-b1a0-93d1ea853194", + "datafile_paths": [ + "underscore/package.json" + ], + "datasource_ids": [ + "npm_package_json" + ], + "purl": "pkg:npm/%40types/underscore@1.10.9" + }, + { + "type": "npm", + "namespace": "@umijs", + "name": "plugin-request", + "version": "2.4.1", + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": "@umijs/plugin-request", + "release_date": null, + "parties": [], + "keywords": [ + "umi" + ], + "homepage_url": "https://github.com/umijs/plugins/tree/master/packages/plugin-request#readme", + "download_url": "https://registry.npmjs.org/@umijs/plugin-request/-/plugin-request-2.4.1.tgz", + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": "http://github.com/umijs/plugins/issues", + "code_view_url": null, + "vcs_url": "git+https://github.com/umijs/plugins", + "copyright": null, + "license_expression": "mit", + "declared_license": [ + "MIT" + ], + "notice_text": null, + "source_packages": [], + "extra_data": {}, + "repository_homepage_url": "https://www.npmjs.com/package/@umijs/plugin-request", + "repository_download_url": "https://registry.npmjs.org/@umijs/plugin-request/-/plugin-request-2.4.1.tgz", + "api_data_url": "https://registry.npmjs.org/@umijs%2fplugin-request", + "package_uid": "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4", + "datafile_paths": [ + "underscore/node_modules/package/package.json" + ], + "datasource_ids": [ + "npm_package_json" + ], + "purl": "pkg:npm/%40umijs/plugin-request@2.4.1" + } + ], + "files": [ + { + "path": "underscore", + "type": "directory", + "name": "underscore", + "base_name": "underscore", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [], + "files_count": 11, + "dirs_count": 4, + "size_count": 244503, + "scan_errors": [] + }, + { + "path": "underscore/index.d.ts", + "type": "file", + "name": "index.d.ts", + "base_name": "index.d", + "extension": ".ts", + "size": 212101, + "date": "2020-07-15", + "sha1": "4f2b85857c3a162c5d536e342ba417fa6c03d40a", + "md5": "ae0acb15531b2efe7253a9bd1fee1b86", + "sha256": "92c3d4b6cf13af286895b484b680b314be34a62a41d1440a2d62d5d5cf0e93b3", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40types/underscore@1.10.9?uuid=420db78a-625f-4622-b1a0-93d1ea853194" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 1141, + "date": "2020-07-15", + "sha1": "689ec0681815ecc32bee639c68e7740add7bd301", + "md5": "d4a904ca135bb7bc912156fee12726f0", + "sha256": "c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40types/underscore@1.10.9?uuid=420db78a-625f-4622-b1a0-93d1ea853194" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 1940, + "date": "2020-07-15", + "sha1": "e751d5da4a71b9eaec5114e8a5b5eceef15d0b4d", + "md5": "a8b2dc907f99ba2e34a97954075d9b8f", + "sha256": "31e806866fe2800471fd3544fc17ed77f9b58885ff8f0590d3dc8d623b897206", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [ + { + "type": "npm", + "namespace": "@types", + "name": "underscore", + "version": "1.10.9", + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": "TypeScript definitions for Underscore", + "release_date": null, + "parties": [ + { + "type": "person", + "role": "contributor", + "name": "Boris Yankov", + "email": null, + "url": "https://github.com/borisyankov" + }, + { + "type": "person", + "role": "contributor", + "name": "Josh Baldwin", + "email": null, + "url": "https://github.com/jbaldwin" + }, + { + "type": "person", + "role": "contributor", + "name": "Christopher Currens", + "email": null, + "url": "https://github.com/ccurrens" + }, + { + "type": "person", + "role": "contributor", + "name": "Ard Timmerman", + "email": null, + "url": "https://github.com/confususs" + }, + { + "type": "person", + "role": "contributor", + "name": "Julian Gonggrijp", + "email": null, + "url": "https://github.com/jgonggrijp" + }, + { + "type": "person", + "role": "contributor", + "name": "Florian Keller", + "email": null, + "url": "https://github.com/ffflorian" + }, + { + "type": "person", + "role": "contributor", + "name": "Regev Brody", + "email": null, + "url": "https://github.com/regevbr" + }, + { + "type": "person", + "role": "contributor", + "name": "Piotr B\u0142a\u017cejewicz", + "email": null, + "url": "https://github.com/peterblazejewicz" + }, + { + "type": "person", + "role": "contributor", + "name": "Michael Ness", + "email": null, + "url": "https://github.com/reubenrybnik" + } + ], + "keywords": [], + "homepage_url": null, + "download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": "git+https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "copyright": null, + "license_expression": "mit", + "declared_license": [ + "MIT" + ], + "notice_text": null, + "source_packages": [], + "file_references": [], + "extra_data": {}, + "dependencies": [], + "repository_homepage_url": "https://www.npmjs.com/package/@types/underscore", + "repository_download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "api_data_url": "https://registry.npmjs.org/@types%2funderscore", + "datasource_id": "npm_package_json", + "purl": "pkg:npm/%40types/underscore@1.10.9" + } + ], + "for_packages": [ + "pkg:npm/%40types/underscore@1.10.9?uuid=420db78a-625f-4622-b1a0-93d1ea853194" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 889, + "date": "2020-07-15", + "sha1": "0a9a39ef14ee88de5e8c4ed6aaeef3ee64f9421f", + "md5": "c8223d72fe3e5f79ac77f9566e7e8e28", + "sha256": "abd4f29b3bf6e302720577ad706f15f68fa13ff227aeeb9e1b091d66e54bc5d9", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines, with CRLF line terminators", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40types/underscore@1.10.9?uuid=420db78a-625f-4622-b1a0-93d1ea853194" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/node_modules", + "type": "directory", + "name": "node_modules", + "base_name": "node_modules", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [], + "files_count": 7, + "dirs_count": 3, + "size_count": 28432, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [], + "files_count": 7, + "dirs_count": 2, + "size_count": 28432, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/CHANGELOG.md", + "type": "file", + "name": "CHANGELOG.md", + "base_name": "CHANGELOG", + "extension": ".md", + "size": 3309, + "date": "1985-10-26", + "sha1": "79625a8ec840826bbab4e6658c53c11ea950fcd3", + "md5": "37600475d22e98ae0e8e1e606e2991e7", + "sha256": "33c8629e8ddd3817357bf31d68b76379cbea6f7588b63df152b0d57f7fdd393b", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 771, + "date": "1985-10-26", + "sha1": "33f98f5756ed1098e7242883aaef2b74dfa06e8d", + "md5": "998d31468d34220e7daf05d41196767c", + "sha256": "15fa5153db3daa38c6bc1d5c5cb73d34064c1c0426cfccb91ea62735a95aedaa", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [ + { + "type": "npm", + "namespace": "@umijs", + "name": "plugin-request", + "version": "2.4.1", + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": "@umijs/plugin-request", + "release_date": null, + "parties": [], + "keywords": [ + "umi" + ], + "homepage_url": "https://github.com/umijs/plugins/tree/master/packages/plugin-request#readme", + "download_url": "https://registry.npmjs.org/@umijs/plugin-request/-/plugin-request-2.4.1.tgz", + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": "http://github.com/umijs/plugins/issues", + "code_view_url": null, + "vcs_url": "git+https://github.com/umijs/plugins", + "copyright": null, + "license_expression": "mit", + "declared_license": [ + "MIT" + ], + "notice_text": null, + "source_packages": [], + "file_references": [], + "extra_data": {}, + "dependencies": [ + { + "purl": "pkg:npm/%40ahooksjs/use-request", + "extracted_requirement": "^2.0.0", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false, + "resolved_package": {}, + "extra_data": {} + }, + { + "purl": "pkg:npm/umi-request", + "extracted_requirement": "^1.2.14", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false, + "resolved_package": {}, + "extra_data": {} + }, + { + "purl": "pkg:npm/umi", + "extracted_requirement": "3.x", + "scope": "peerDependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false, + "resolved_package": {}, + "extra_data": {} + } + ], + "repository_homepage_url": "https://www.npmjs.com/package/@umijs/plugin-request", + "repository_download_url": "https://registry.npmjs.org/@umijs/plugin-request/-/plugin-request-2.4.1.tgz", + "api_data_url": "https://registry.npmjs.org/@umijs%2fplugin-request", + "datasource_id": "npm_package_json", + "purl": "pkg:npm/%40umijs/plugin-request@2.4.1" + } + ], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 307, + "date": "1985-10-26", + "sha1": "fac8486d1aef8658ae89d37c70b0edb332262109", + "md5": "a3cb10e52239977c38a699ef518dc8d4", + "sha256": "886cd0e0fafc00de3f8726a27920e449f09767dbf19aa14b4e65dc1bbdb168c7", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/lib", + "type": "directory", + "name": "lib", + "base_name": "lib", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "files_count": 2, + "dirs_count": 0, + "size_count": 13964, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/lib/index.js", + "type": "file", + "name": "index.js", + "base_name": "index", + "extension": ".js", + "size": 2453, + "date": "1985-10-26", + "sha1": "aecb2790e91e0eb8f1f8608c4ac7a689a9a28467", + "md5": "0a4e5a9b46acd86352121a86f2ad6ca0", + "sha256": "8f3e85de5fec26a6c76b17170fa3671f2f0ff05efe7751339ca41a2f90b9a1b0", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text, with very long lines", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/lib/request.js", + "type": "file", + "name": "request.js", + "base_name": "request", + "extension": ".js", + "size": 11511, + "date": "1985-10-26", + "sha1": "e59e280693fc62d2a7e902475e5e13d3bf62d476", + "md5": "e00da7bc6c7649c392bc9e20cd779167", + "sha256": "9ae43cf04cc4babc3d2d6dd54f6584da5c2749018e64d396d1e06181d3c4b276", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/src", + "type": "directory", + "name": "src", + "base_name": "src", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "files_count": 2, + "dirs_count": 0, + "size_count": 10081, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/src/index.ts", + "type": "file", + "name": "index.ts", + "base_name": "index", + "extension": ".ts", + "size": 2138, + "date": "1985-10-26", + "sha1": "00216c5fd157602a28b45530c6badb388405820d", + "md5": "3fa996e7e53a7447bd5e8d3455ac29b6", + "sha256": "d1d94851002539609e3b20a36594d25f10a6a8d9bd42d2e7c6b3d4f243ab52e4", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/node_modules/package/src/request.ts", + "type": "file", + "name": "request.ts", + "base_name": "request", + "extension": ".ts", + "size": 7943, + "date": "1985-10-26", + "sha1": "c6b37045e7919219efffd83894df441e47c87cfb", + "md5": "7a77806ca3e1452cc7eb5d01a35f6264", + "sha256": "25bbbc1924dcb7a726142be254d12ace5379fbee9bf37e20a7ce300bb01030cb", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40umijs/plugin-request@2.4.1?uuid=923ada0a-c8f6-41f1-8b9c-04e2a8537ef4" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/nested/plugin-request-2.4.1-ip.json b/matchcode/tests/matchcode/testfiles/match/nested/plugin-request-2.4.1-ip.json new file mode 100644 index 00000000..8eb09da8 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/nested/plugin-request-2.4.1-ip.json @@ -0,0 +1,353 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "3.2.2rc3", + "options": { + "input": [ + "/tmp/test/package/" + ], + "--info": true, + "--json-pp": "/tmp/test/plugin-request-2.4.1-ip.json", + "--package": true, + "--processes": "2" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2020-11-07T201424.972560", + "end_timestamp": "2020-11-07T201429.280526", + "duration": 4.30798077583313, + "message": null, + "errors": [], + "extra_data": { + "files_count": 7 + } + } + ], + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 7, + "dirs_count": 2, + "size_count": 28432, + "scan_errors": [] + }, + { + "path": "package/CHANGELOG.md", + "type": "file", + "name": "CHANGELOG.md", + "base_name": "CHANGELOG", + "extension": ".md", + "size": 3309, + "date": "1985-10-26", + "sha1": "79625a8ec840826bbab4e6658c53c11ea950fcd3", + "md5": "37600475d22e98ae0e8e1e606e2991e7", + "sha256": "33c8629e8ddd3817357bf31d68b76379cbea6f7588b63df152b0d57f7fdd393b", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 771, + "date": "1985-10-26", + "sha1": "33f98f5756ed1098e7242883aaef2b74dfa06e8d", + "md5": "998d31468d34220e7daf05d41196767c", + "sha256": "15fa5153db3daa38c6bc1d5c5cb73d34064c1c0426cfccb91ea62735a95aedaa", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [ + { + "type": "npm", + "namespace": "@umijs", + "name": "plugin-request", + "version": "2.4.1", + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": "@umijs/plugin-request", + "release_date": null, + "parties": [], + "keywords": [ + "umi" + ], + "homepage_url": "https://github.com/umijs/plugins/tree/master/packages/plugin-request#readme", + "download_url": "https://registry.npmjs.org/@umijs/plugin-request/-/plugin-request-2.4.1.tgz", + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": "http://github.com/umijs/plugins/issues", + "code_view_url": null, + "vcs_url": "git+https://github.com/umijs/plugins", + "copyright": null, + "license_expression": "mit", + "declared_license": [ + "MIT" + ], + "notice_text": null, + "root_path": "package", + "dependencies": [ + { + "purl": "pkg:npm/%40ahooksjs/use-request", + "requirement": "^2.0.0", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false + }, + { + "purl": "pkg:npm/umi-request", + "requirement": "^1.2.14", + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false + }, + { + "purl": "pkg:npm/umi", + "requirement": "3.x", + "scope": "peerDependencies", + "is_runtime": true, + "is_optional": false, + "is_resolved": false + } + ], + "contains_source_code": null, + "source_packages": [], + "purl": "pkg:npm/%40umijs/plugin-request@2.4.1", + "repository_homepage_url": "https://www.npmjs.com/package/@umijs/plugin-request", + "repository_download_url": "https://registry.npmjs.org/@umijs/plugin-request/-/plugin-request-2.4.1.tgz", + "api_data_url": "https://registry.npmjs.org/@umijs%2fplugin-request" + } + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 307, + "date": "1985-10-26", + "sha1": "fac8486d1aef8658ae89d37c70b0edb332262109", + "md5": "a3cb10e52239977c38a699ef518dc8d4", + "sha256": "886cd0e0fafc00de3f8726a27920e449f09767dbf19aa14b4e65dc1bbdb168c7", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/lib", + "type": "directory", + "name": "lib", + "base_name": "lib", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 2, + "dirs_count": 0, + "size_count": 13964, + "scan_errors": [] + }, + { + "path": "package/lib/index.js", + "type": "file", + "name": "index.js", + "base_name": "index", + "extension": ".js", + "size": 2453, + "date": "1985-10-26", + "sha1": "aecb2790e91e0eb8f1f8608c4ac7a689a9a28467", + "md5": "0a4e5a9b46acd86352121a86f2ad6ca0", + "sha256": "8f3e85de5fec26a6c76b17170fa3671f2f0ff05efe7751339ca41a2f90b9a1b0", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text, with very long lines", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/lib/request.js", + "type": "file", + "name": "request.js", + "base_name": "request", + "extension": ".js", + "size": 11511, + "date": "1985-10-26", + "sha1": "e59e280693fc62d2a7e902475e5e13d3bf62d476", + "md5": "e00da7bc6c7649c392bc9e20cd779167", + "sha256": "9ae43cf04cc4babc3d2d6dd54f6584da5c2749018e64d396d1e06181d3c4b276", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/src", + "type": "directory", + "name": "src", + "base_name": "src", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 2, + "dirs_count": 0, + "size_count": 10081, + "scan_errors": [] + }, + { + "path": "package/src/index.ts", + "type": "file", + "name": "index.ts", + "base_name": "index", + "extension": ".ts", + "size": 2138, + "date": "1985-10-26", + "sha1": "00216c5fd157602a28b45530c6badb388405820d", + "md5": "3fa996e7e53a7447bd5e8d3455ac29b6", + "sha256": "d1d94851002539609e3b20a36594d25f10a6a8d9bd42d2e7c6b3d4f243ab52e4", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/src/request.ts", + "type": "file", + "name": "request.ts", + "base_name": "request", + "extension": ".ts", + "size": 7943, + "date": "1985-10-26", + "sha1": "c6b37045e7919219efffd83894df441e47c87cfb", + "md5": "7a77806ca3e1452cc7eb5d01a35f6264", + "sha256": "25bbbc1924dcb7a726142be254d12ace5379fbee9bf37e20a7ce300bb01030cb", + "mime_type": "text/x-java", + "file_type": "Java source, UTF-8 Unicode text", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/nested/underscore-1.10.9-ip.json b/matchcode/tests/matchcode/testfiles/match/nested/underscore-1.10.9-ip.json new file mode 100644 index 00000000..e2a6011d --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/nested/underscore-1.10.9-ip.json @@ -0,0 +1,260 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "3.2.2rc3", + "options": { + "input": [ + "/tmp/test/underscore/" + ], + "--info": true, + "--json-pp": "/tmp/test/underscore-ip.json", + "--package": true, + "--processes": "2" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2020-11-07T201338.623038", + "end_timestamp": "2020-11-07T201343.409115", + "duration": 4.78609824180603, + "message": null, + "errors": [], + "extra_data": { + "files_count": 4 + } + } + ], + "files": [ + { + "path": "underscore", + "type": "directory", + "name": "underscore", + "base_name": "underscore", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 4, + "dirs_count": 0, + "size_count": 216071, + "scan_errors": [] + }, + { + "path": "underscore/index.d.ts", + "type": "file", + "name": "index.d.ts", + "base_name": "index.d", + "extension": ".ts", + "size": 212101, + "date": "2020-07-15", + "sha1": "4f2b85857c3a162c5d536e342ba417fa6c03d40a", + "md5": "ae0acb15531b2efe7253a9bd1fee1b86", + "sha256": "92c3d4b6cf13af286895b484b680b314be34a62a41d1440a2d62d5d5cf0e93b3", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 1141, + "date": "2020-07-15", + "sha1": "689ec0681815ecc32bee639c68e7740add7bd301", + "md5": "d4a904ca135bb7bc912156fee12726f0", + "sha256": "c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 1940, + "date": "2020-07-15", + "sha1": "e751d5da4a71b9eaec5114e8a5b5eceef15d0b4d", + "md5": "a8b2dc907f99ba2e34a97954075d9b8f", + "sha256": "31e806866fe2800471fd3544fc17ed77f9b58885ff8f0590d3dc8d623b897206", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [ + { + "type": "npm", + "namespace": "@types", + "name": "underscore", + "version": "1.10.9", + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": "TypeScript definitions for Underscore", + "release_date": null, + "parties": [ + { + "type": "person", + "role": "contributor", + "name": "Boris Yankov", + "email": null, + "url": "https://github.com/borisyankov" + }, + { + "type": "person", + "role": "contributor", + "name": "Josh Baldwin", + "email": null, + "url": "https://github.com/jbaldwin" + }, + { + "type": "person", + "role": "contributor", + "name": "Christopher Currens", + "email": null, + "url": "https://github.com/ccurrens" + }, + { + "type": "person", + "role": "contributor", + "name": "Ard Timmerman", + "email": null, + "url": "https://github.com/confususs" + }, + { + "type": "person", + "role": "contributor", + "name": "Julian Gonggrijp", + "email": null, + "url": "https://github.com/jgonggrijp" + }, + { + "type": "person", + "role": "contributor", + "name": "Florian Keller", + "email": null, + "url": "https://github.com/ffflorian" + }, + { + "type": "person", + "role": "contributor", + "name": "Regev Brody", + "email": null, + "url": "https://github.com/regevbr" + }, + { + "type": "person", + "role": "contributor", + "name": "Piotr B\u0142a\u017cejewicz", + "email": null, + "url": "https://github.com/peterblazejewicz" + }, + { + "type": "person", + "role": "contributor", + "name": "Michael Ness", + "email": null, + "url": "https://github.com/reubenrybnik" + } + ], + "keywords": [], + "homepage_url": null, + "download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": "git+https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "copyright": null, + "license_expression": "mit", + "declared_license": [ + "MIT" + ], + "notice_text": null, + "root_path": "underscore", + "dependencies": [], + "contains_source_code": null, + "source_packages": [], + "purl": "pkg:npm/%40types/underscore@1.10.9", + "repository_homepage_url": "https://www.npmjs.com/package/@types/underscore", + "repository_download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "api_data_url": "https://registry.npmjs.org/@types%2funderscore" + } + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 889, + "date": "2020-07-15", + "sha1": "0a9a39ef14ee88de5e8c4ed6aaeef3ee64f9421f", + "md5": "c8223d72fe3e5f79ac77f9566e7e8e28", + "sha256": "abd4f29b3bf6e302720577ad706f15f68fa13ff227aeeb9e1b091d66e54bc5d9", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines, with CRLF line terminators", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/nested/underscore.json b/matchcode/tests/matchcode/testfiles/match/nested/underscore.json new file mode 100644 index 00000000..3aca6658 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/nested/underscore.json @@ -0,0 +1,392 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "31.2.2", + "options": { + "input": [ + "underscore" + ], + "--info": true, + "--json-pp": "underscore.json", + "--package": true + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2022-12-01T201941.639402", + "end_timestamp": "2022-12-01T202115.845138", + "output_format_version": "2.0.0", + "duration": 94.20575189590454, + "message": null, + "errors": [], + "warnings": [], + "extra_data": { + "system_environment": { + "operating_system": "linux", + "cpu_architecture": "64", + "platform": "Linux-5.15.39-3-pve-x86_64-with-glibc2.35", + "platform_version": "#2 SMP PVE 5.15.39-3 (Wed, 27 Jul 2022 13:45:39 +0200)", + "python_version": "3.10.5 (main, Jul 30 2022, 06:09:26) [GCC 9.4.0]" + }, + "spdx_license_list_version": "3.17", + "files_count": 4 + } + } + ], + "dependencies": [], + "packages": [ + { + "type": "npm", + "namespace": "@types", + "name": "underscore", + "version": "1.10.9", + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": "TypeScript definitions for Underscore", + "release_date": null, + "parties": [ + { + "type": "person", + "role": "contributor", + "name": "Boris Yankov", + "email": null, + "url": "https://github.com/borisyankov" + }, + { + "type": "person", + "role": "contributor", + "name": "Josh Baldwin", + "email": null, + "url": "https://github.com/jbaldwin" + }, + { + "type": "person", + "role": "contributor", + "name": "Christopher Currens", + "email": null, + "url": "https://github.com/ccurrens" + }, + { + "type": "person", + "role": "contributor", + "name": "Ard Timmerman", + "email": null, + "url": "https://github.com/confususs" + }, + { + "type": "person", + "role": "contributor", + "name": "Julian Gonggrijp", + "email": null, + "url": "https://github.com/jgonggrijp" + }, + { + "type": "person", + "role": "contributor", + "name": "Florian Keller", + "email": null, + "url": "https://github.com/ffflorian" + }, + { + "type": "person", + "role": "contributor", + "name": "Regev Brody", + "email": null, + "url": "https://github.com/regevbr" + }, + { + "type": "person", + "role": "contributor", + "name": "Piotr B\u0142a\u017cejewicz", + "email": null, + "url": "https://github.com/peterblazejewicz" + }, + { + "type": "person", + "role": "contributor", + "name": "Michael Ness", + "email": null, + "url": "https://github.com/reubenrybnik" + } + ], + "keywords": [], + "homepage_url": null, + "download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": "git+https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "copyright": null, + "license_expression": "mit", + "declared_license": [ + "MIT" + ], + "notice_text": null, + "source_packages": [], + "extra_data": {}, + "repository_homepage_url": "https://www.npmjs.com/package/@types/underscore", + "repository_download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "api_data_url": "https://registry.npmjs.org/@types%2funderscore", + "package_uid": "pkg:npm/%40types/underscore@1.10.9?uuid=1cce1a48-ced8-4bec-a349-87d895e96973", + "datafile_paths": [ + "underscore/package.json" + ], + "datasource_ids": [ + "npm_package_json" + ], + "purl": "pkg:npm/%40types/underscore@1.10.9" + } + ], + "files": [ + { + "path": "underscore", + "type": "directory", + "name": "underscore", + "base_name": "underscore", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [], + "files_count": 4, + "dirs_count": 0, + "size_count": 216071, + "scan_errors": [] + }, + { + "path": "underscore/index.d.ts", + "type": "file", + "name": "index.d.ts", + "base_name": "index.d", + "extension": ".ts", + "size": 212101, + "date": "2020-07-15", + "sha1": "4f2b85857c3a162c5d536e342ba417fa6c03d40a", + "md5": "ae0acb15531b2efe7253a9bd1fee1b86", + "sha256": "92c3d4b6cf13af286895b484b680b314be34a62a41d1440a2d62d5d5cf0e93b3", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines", + "programming_language": "TypeScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40types/underscore@1.10.9?uuid=1cce1a48-ced8-4bec-a349-87d895e96973" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 1141, + "date": "2020-07-15", + "sha1": "689ec0681815ecc32bee639c68e7740add7bd301", + "md5": "d4a904ca135bb7bc912156fee12726f0", + "sha256": "c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40types/underscore@1.10.9?uuid=1cce1a48-ced8-4bec-a349-87d895e96973" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 1940, + "date": "2020-07-15", + "sha1": "e751d5da4a71b9eaec5114e8a5b5eceef15d0b4d", + "md5": "a8b2dc907f99ba2e34a97954075d9b8f", + "sha256": "31e806866fe2800471fd3544fc17ed77f9b58885ff8f0590d3dc8d623b897206", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [ + { + "type": "npm", + "namespace": "@types", + "name": "underscore", + "version": "1.10.9", + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": "TypeScript definitions for Underscore", + "release_date": null, + "parties": [ + { + "type": "person", + "role": "contributor", + "name": "Boris Yankov", + "email": null, + "url": "https://github.com/borisyankov" + }, + { + "type": "person", + "role": "contributor", + "name": "Josh Baldwin", + "email": null, + "url": "https://github.com/jbaldwin" + }, + { + "type": "person", + "role": "contributor", + "name": "Christopher Currens", + "email": null, + "url": "https://github.com/ccurrens" + }, + { + "type": "person", + "role": "contributor", + "name": "Ard Timmerman", + "email": null, + "url": "https://github.com/confususs" + }, + { + "type": "person", + "role": "contributor", + "name": "Julian Gonggrijp", + "email": null, + "url": "https://github.com/jgonggrijp" + }, + { + "type": "person", + "role": "contributor", + "name": "Florian Keller", + "email": null, + "url": "https://github.com/ffflorian" + }, + { + "type": "person", + "role": "contributor", + "name": "Regev Brody", + "email": null, + "url": "https://github.com/regevbr" + }, + { + "type": "person", + "role": "contributor", + "name": "Piotr B\u0142a\u017cejewicz", + "email": null, + "url": "https://github.com/peterblazejewicz" + }, + { + "type": "person", + "role": "contributor", + "name": "Michael Ness", + "email": null, + "url": "https://github.com/reubenrybnik" + } + ], + "keywords": [], + "homepage_url": null, + "download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": "git+https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "copyright": null, + "license_expression": "mit", + "declared_license": [ + "MIT" + ], + "notice_text": null, + "source_packages": [], + "file_references": [], + "extra_data": {}, + "dependencies": [], + "repository_homepage_url": "https://www.npmjs.com/package/@types/underscore", + "repository_download_url": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.9.tgz", + "api_data_url": "https://registry.npmjs.org/@types%2funderscore", + "datasource_id": "npm_package_json", + "purl": "pkg:npm/%40types/underscore@1.10.9" + } + ], + "for_packages": [ + "pkg:npm/%40types/underscore@1.10.9?uuid=1cce1a48-ced8-4bec-a349-87d895e96973" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "underscore/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 889, + "date": "2020-07-15", + "sha1": "0a9a39ef14ee88de5e8c4ed6aaeef3ee64f9421f", + "md5": "c8223d72fe3e5f79ac77f9566e7e8e28", + "sha256": "abd4f29b3bf6e302720577ad706f15f68fa13ff227aeeb9e1b091d66e54bc5d9", + "mime_type": "text/plain", + "file_type": "UTF-8 Unicode text, with very long lines, with CRLF line terminators", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "package_data": [], + "for_packages": [ + "pkg:npm/%40types/underscore@1.10.9?uuid=1cce1a48-ced8-4bec-a349-87d895e96973" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/match/scan1.json b/matchcode/tests/matchcode/testfiles/match/scan1.json new file mode 100644 index 00000000..ee0458e8 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/match/scan1.json @@ -0,0 +1,135 @@ +{ + "scancode_notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "scancode_version": "2.9.1", + "scancode_options": { + "input": "/testfiles/test", + "--fingerprint": true, + "--info": true, + "--json": "/testfiles/out_test.json" + }, + "files_count": 2, + "files": [ + { + "path": "test", + "type": "directory", + "name": "test", + "base_name": "test", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + + "fingerprints": { + "merkle_bah128": "8f52dcee236ae3fedaf86ca5fb13a9f4", + "merkle_sha1": "9fa0275b959bae94f9607c7af6cb89bae231440f" + }, + "files_count": 2, + "dirs_count": 1, + "size_count": 2, + "scan_errors": [] + }, + { + "path": "test/a", + "type": "file", + "name": "a", + "base_name": "a", + "extension": "", + "size": 1, + "date": "2018-05-08", + "sha1": "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8", + "md5": "0cc175b9c0f1b6a831c399e269772661", + "mime_type": "application/octet-stream", + "file_type": "very short file (no magic)", + "programming_language": "Text only", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + + "fingerprints": { + "bah128": "0cc175b9c0f1b6a831c399e269772661", + "hailstorm": null, + "sha1_git": "2e65efe2a145dda7ee51d1741299f848e5bf752e", + "sha256": "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", + "sha512": "1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75" + }, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/dir", + "type": "directory", + "name": "dir", + "base_name": "dir", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + + "fingerprints": { + "merkle_bah128": "dc0ae7e1387be9b795f5d6299e383759", + "merkle_sha1": "b035ee5669cac0d97397333030ab262caa966dea" + }, + "files_count": 1, + "dirs_count": 0, + "size_count": 1, + "scan_errors": [] + }, + { + "path": "test/dir/b", + "type": "file", + "name": "b", + "base_name": "b", + "extension": "", + "size": 1, + "date": "2018-05-08", + "sha1": "e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98", + "md5": "92eb5ffee6ae2fec3ad71c777531578f", + "mime_type": "application/octet-stream", + "file_type": "very short file (no magic)", + "programming_language": "Text only", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + + "fingerprints": { + "bah128": "92eb5ffee6ae2fec3ad71c777531578f", + "hailstorm": null, + "sha1_git": "63d8dbd40c23542e740659a7168a0ce3138ea748", + "sha256": "3e23e8160039594a33894f6564e1b1348bbd7a0088d42c4acb73eeaed59c009d", + "sha512": "5267768822ee624d48fce15ec5ca79cbd602cb7f4c2157a516556991f22ef8c7b5ef7b18d1ff41c59370efb0858651d44a936c11b7b144c48fe04df3c6a3e8da" + }, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} diff --git a/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.10.tgz-i.json b/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.10.tgz-i.json new file mode 100644 index 00000000..8410bc05 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.10.tgz-i.json @@ -0,0 +1,201 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "3.2.3", + "options": { + "input": [ + "package/" + ], + "--info": true, + "--json-pp": "async-0.2.10.tgz-i.json" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2021-02-17T021750.899853", + "end_timestamp": "2021-02-17T021751.010377", + "duration": 0.11053991317749023, + "message": null, + "errors": [], + "extra_data": { + "files_count": 5 + } + } + ], + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 5, + "dirs_count": 1, + "size_count": 74685, + "scan_errors": [] + }, + { + "path": "package/component.json", + "type": "file", + "name": "component.json", + "base_name": "component", + "extension": ".json", + "size": 276, + "date": "2013-05-28", + "sha1": "6615a8e63ada0d9d145cc38ff6e1421a1f857742", + "md5": "aec01451771f878693dda7bf040642ae", + "sha256": "658e07147ddb1458d81d21ff46271e8bd67cb36b1922701b11cf0dc4143c1eea", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 1058, + "date": "2013-05-28", + "sha1": "99e50b7dfe39753bccb8aabce3cd1f80e960713b", + "md5": "64a378b2b01424fe22d54bc626175994", + "sha256": "b04b9e208e566fa898c7429e4dd5b45ba3ba2f7391e5c009cf63c53d580fa9b4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 876, + "date": "2014-01-23", + "sha1": "17cc2a4f858056d0cba767c580ff3b05b61abee8", + "md5": "b9af078ffbadf94f89007b64419058ef", + "sha256": "d3c8c237e2eb2f97718b77e57103c6de5aab5d81ffad3b68f634b608d99b2b12", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 43074, + "date": "2014-01-23", + "sha1": "f9a4700b8390398540777021cc858f4efb2b4b02", + "md5": "c0900735f205ab79ef2d35762ee7d55c", + "sha256": "d0ee397ceb1814a74117d363934796392ea33448c4b3769579adbde82ebd2979", + "mime_type": "text/html", + "file_type": "HTML document, UTF-8 Unicode text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/lib", + "type": "directory", + "name": "lib", + "base_name": "lib", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 1, + "dirs_count": 0, + "size_count": 29401, + "scan_errors": [] + }, + { + "path": "package/lib/async.js", + "type": "file", + "name": "async.js", + "base_name": "async", + "extension": ".js", + "size": 29401, + "date": "2014-01-23", + "sha1": "b557301146872e38bde36f81efddb1aa284cf239", + "md5": "fe09e04d0bd97bdc821e560491b10231", + "sha256": "665d0187f6a7c44135a6b9fceb2a234f960893a22f3ed632e53b2fe934c3e6eb", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i-expected-content.json b/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i-expected-content.json new file mode 100644 index 00000000..c7ce4ce2 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i-expected-content.json @@ -0,0 +1,224 @@ +{ + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [ + { + "type": "npm", + "namespace": "", + "name": "async", + "version": "0.2.9", + "qualifiers": "", + "subpath": "", + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", + "size": 15672, + "md5": "895ac62ba7c61086cffdd50ab03c0447", + "sha1": "df63060fbf3d33286a76aaf6d55a2986d9ff8619", + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "license_expression": null, + "declared_license": null, + "notice_text": null, + "source_packages": [], + "extra_data": {}, + "api_data_url": null, + "datasource_id": null, + "purl": "pkg:npm/async@0.2.9", + "manifest_path": null, + "contains_source_code": null, + "file_references": [], + "dependencies": [], + "match_type": "approximate-directory-content" + } + ], + "files_count": 5, + "dirs_count": 1, + "size_count": 74218, + "scan_errors": [] + }, + { + "path": "package/component.json", + "type": "file", + "name": "component.json", + "base_name": "component", + "extension": ".json", + "size": 276, + "date": "2013-05-28", + "sha1": "6615a8e63ada0d9d145cc38ff6e1421a1f857742", + "md5": "aec01451771f878693dda7bf040642ae", + "sha256": "658e07147ddb1458d81d21ff46271e8bd67cb36b1922701b11cf0dc4143c1eea", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 1058, + "date": "2013-05-28", + "sha1": "99e50b7dfe39753bccb8aabce3cd1f80e960713b", + "md5": "64a378b2b01424fe22d54bc626175994", + "sha256": "b04b9e208e566fa898c7429e4dd5b45ba3ba2f7391e5c009cf63c53d580fa9b4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 875, + "date": "2013-05-28", + "sha1": "89ee3da95d9be9514d58eec01c1a2a5aa450b5e7", + "md5": "eae59bcc1a34df541efb423cee0653d1", + "sha256": "0a05706c3026dc78ee73eb92f1feb39bef2defef6f32f2aa79a631bfe3ebe2ea", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 42716, + "date": "2013-05-28", + "sha1": "6ea922136bd1a91fe457a4238934820d84cf1093", + "md5": "374b909e5200bdf8a96e80ca5c65da21", + "sha256": "51405d87248ea2c2c5acd226281e77d4c2fb07eff2ccf640094cd2fbbfa4ab00", + "mime_type": "text/html", + "file_type": "HTML document, UTF-8 Unicode text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/lib", + "type": "directory", + "name": "lib", + "base_name": "lib", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 1, + "dirs_count": 0, + "size_count": 29293, + "scan_errors": [] + }, + { + "path": "package/lib/async.js", + "type": "file", + "name": "async.js", + "base_name": "async", + "extension": ".js", + "size": 29293, + "date": "2013-05-28", + "sha1": "31ed74fe94a795e7beac5d904dd567196666a011", + "md5": "2ff89a2726f7745d1ad89393afe4f0ab", + "sha256": "df5a89ada496897b43a02ca698060bfd4f1d9852d5205348a65289d8aaacb85a", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i-expected-structure.json b/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i-expected-structure.json new file mode 100644 index 00000000..b830a2aa --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i-expected-structure.json @@ -0,0 +1,224 @@ +{ + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [ + { + "type": "npm", + "namespace": "", + "name": "async", + "version": "0.2.9", + "qualifiers": "", + "subpath": "", + "primary_language": null, + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", + "size": 15672, + "md5": "895ac62ba7c61086cffdd50ab03c0447", + "sha1": "df63060fbf3d33286a76aaf6d55a2986d9ff8619", + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "license_expression": null, + "declared_license": null, + "notice_text": null, + "source_packages": [], + "extra_data": {}, + "api_data_url": null, + "datasource_id": null, + "purl": "pkg:npm/async@0.2.9", + "manifest_path": null, + "contains_source_code": null, + "file_references": [], + "dependencies": [], + "match_type": "approximate-directory-structure" + } + ], + "files_count": 5, + "dirs_count": 1, + "size_count": 74218, + "scan_errors": [] + }, + { + "path": "package/component.json", + "type": "file", + "name": "component.json", + "base_name": "component", + "extension": ".json", + "size": 276, + "date": "2013-05-28", + "sha1": "6615a8e63ada0d9d145cc38ff6e1421a1f857742", + "md5": "aec01451771f878693dda7bf040642ae", + "sha256": "658e07147ddb1458d81d21ff46271e8bd67cb36b1922701b11cf0dc4143c1eea", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 1058, + "date": "2013-05-28", + "sha1": "99e50b7dfe39753bccb8aabce3cd1f80e960713b", + "md5": "64a378b2b01424fe22d54bc626175994", + "sha256": "b04b9e208e566fa898c7429e4dd5b45ba3ba2f7391e5c009cf63c53d580fa9b4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 875, + "date": "2013-05-28", + "sha1": "89ee3da95d9be9514d58eec01c1a2a5aa450b5e7", + "md5": "eae59bcc1a34df541efb423cee0653d1", + "sha256": "0a05706c3026dc78ee73eb92f1feb39bef2defef6f32f2aa79a631bfe3ebe2ea", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 42716, + "date": "2013-05-28", + "sha1": "6ea922136bd1a91fe457a4238934820d84cf1093", + "md5": "374b909e5200bdf8a96e80ca5c65da21", + "sha256": "51405d87248ea2c2c5acd226281e77d4c2fb07eff2ccf640094cd2fbbfa4ab00", + "mime_type": "text/html", + "file_type": "HTML document, UTF-8 Unicode text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/lib", + "type": "directory", + "name": "lib", + "base_name": "lib", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "packages": [], + "files_count": 1, + "dirs_count": 0, + "size_count": 29293, + "scan_errors": [] + }, + { + "path": "package/lib/async.js", + "type": "file", + "name": "async.js", + "base_name": "async", + "extension": ".js", + "size": 29293, + "date": "2013-05-28", + "sha1": "31ed74fe94a795e7beac5d904dd567196666a011", + "md5": "2ff89a2726f7745d1ad89393afe4f0ab", + "sha256": "df5a89ada496897b43a02ca698060bfd4f1d9852d5205348a65289d8aaacb85a", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "packages": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i.json b/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i.json new file mode 100644 index 00000000..577beae4 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i.json @@ -0,0 +1,201 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "3.2.3.post38.b14ca7b2a.dirty.20210223014938", + "options": { + "input": [ + "package/" + ], + "--info": true, + "--json-pp": "async-0.2.9-i.json" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2021-02-23T232718.544871", + "end_timestamp": "2021-02-23T232718.773712", + "duration": 0.2288661003112793, + "message": null, + "errors": [], + "extra_data": { + "files_count": 5 + } + } + ], + "files": [ + { + "path": "package", + "type": "directory", + "name": "package", + "base_name": "package", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 5, + "dirs_count": 1, + "size_count": 74218, + "scan_errors": [] + }, + { + "path": "package/component.json", + "type": "file", + "name": "component.json", + "base_name": "component", + "extension": ".json", + "size": 276, + "date": "2013-05-28", + "sha1": "6615a8e63ada0d9d145cc38ff6e1421a1f857742", + "md5": "aec01451771f878693dda7bf040642ae", + "sha256": "658e07147ddb1458d81d21ff46271e8bd67cb36b1922701b11cf0dc4143c1eea", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/LICENSE", + "type": "file", + "name": "LICENSE", + "base_name": "LICENSE", + "extension": "", + "size": 1058, + "date": "2013-05-28", + "sha1": "99e50b7dfe39753bccb8aabce3cd1f80e960713b", + "md5": "64a378b2b01424fe22d54bc626175994", + "sha256": "b04b9e208e566fa898c7429e4dd5b45ba3ba2f7391e5c009cf63c53d580fa9b4", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/package.json", + "type": "file", + "name": "package.json", + "base_name": "package", + "extension": ".json", + "size": 875, + "date": "2013-05-28", + "sha1": "89ee3da95d9be9514d58eec01c1a2a5aa450b5e7", + "md5": "eae59bcc1a34df541efb423cee0653d1", + "sha256": "0a05706c3026dc78ee73eb92f1feb39bef2defef6f32f2aa79a631bfe3ebe2ea", + "mime_type": "application/json", + "file_type": "JSON data", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/README.md", + "type": "file", + "name": "README.md", + "base_name": "README", + "extension": ".md", + "size": 42716, + "date": "2013-05-28", + "sha1": "6ea922136bd1a91fe457a4238934820d84cf1093", + "md5": "374b909e5200bdf8a96e80ca5c65da21", + "sha256": "51405d87248ea2c2c5acd226281e77d4c2fb07eff2ccf640094cd2fbbfa4ab00", + "mime_type": "text/html", + "file_type": "HTML document, UTF-8 Unicode text", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "package/lib", + "type": "directory", + "name": "lib", + "base_name": "lib", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 1, + "dirs_count": 0, + "size_count": 29293, + "scan_errors": [] + }, + { + "path": "package/lib/async.js", + "type": "file", + "name": "async.js", + "base_name": "async", + "extension": ".js", + "size": 29293, + "date": "2013-05-28", + "sha1": "31ed74fe94a795e7beac5d904dd567196666a011", + "md5": "2ff89a2726f7745d1ad89393afe4f0ab", + "sha256": "df5a89ada496897b43a02ca698060bfd4f1d9852d5205348a65289d8aaacb85a", + "mime_type": "text/plain", + "file_type": "ASCII text", + "programming_language": "JavaScript", + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": true, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/models/exact-file-matching-standalone-test-results.json b/matchcode/tests/matchcode/testfiles/models/exact-file-matching-standalone-test-results.json new file mode 100644 index 00000000..0e517fe9 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/models/exact-file-matching-standalone-test-results.json @@ -0,0 +1,187 @@ +{ + "files": [ + { + "path": "test", + "type": "directory", + "name": "test", + "base_name": "test", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [], + "files_count": 4, + "dirs_count": 2, + "size_count": 1743469, + "scan_errors": [] + }, + { + "path": "test/c", + "type": "file", + "name": "c", + "base_name": "c", + "extension": "", + "size": 4, + "date": "2020-06-19", + "sha1": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", + "md5": "098f6bcd4621d373cade4e832627b4f6", + "mime_type": "text/plain", + "file_type": "ASCII text, with no line terminators", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/a", + "type": "directory", + "name": "a", + "base_name": "a", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [], + "files_count": 2, + "dirs_count": 0, + "size_count": 1053674, + "scan_errors": [] + }, + { + "path": "test/a/acegi-security-0.51.jar", + "type": "file", + "name": "acegi-security-0.51.jar", + "base_name": "acegi-security-0.51", + "extension": ".jar", + "size": 176954, + "date": "2020-06-19", + "sha1": "ede156692b33872f5ee9465b7a06d6b2bc9e5e7f", + "md5": "19dad3908042b2bdc50cbfdaed7da200", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/a/dojoz-0.4.1-1.jar", + "type": "file", + "name": "dojoz-0.4.1-1.jar", + "base_name": "dojoz-0.4.1-1", + "extension": ".jar", + "size": 876720, + "date": "2020-06-19", + "sha1": "ae9d68fd6a29906606c2d9407d1cc0749ef84588", + "md5": "508361a1c6273a4c2b8e4945618b509f", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/b", + "type": "directory", + "name": "b", + "base_name": "b", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [], + "files_count": 1, + "dirs_count": 0, + "size_count": 689791, + "scan_errors": [] + }, + { + "path": "test/b/abbot-0.12.3.jar", + "type": "file", + "name": "abbot-0.12.3.jar", + "base_name": "abbot-0.12.3", + "extension": ".jar", + "size": 689791, + "date": "2020-05-27", + "sha1": "51d28a27d919ce8690a40f4f335b9d591ceb16e9", + "md5": "38206e62a54b0489fb6baa4db5a06093", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/models/match-test-approximate-directory-content-results.json b/matchcode/tests/matchcode/testfiles/models/match-test-approximate-directory-content-results.json new file mode 100644 index 00000000..4e1b2981 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/models/match-test-approximate-directory-content-results.json @@ -0,0 +1,193 @@ +{ + "files": [ + { + "path": "test", + "type": "directory", + "name": "test", + "base_name": "test", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 4, + "dirs_count": 2, + "size_count": 1743469, + "scan_errors": [] + }, + { + "path": "test/c", + "type": "file", + "name": "c", + "base_name": "c", + "extension": "", + "size": 4, + "date": "2020-06-19", + "sha1": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", + "md5": "098f6bcd4621d373cade4e832627b4f6", + "mime_type": "text/plain", + "file_type": "ASCII text, with no line terminators", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/a", + "type": "directory", + "name": "a", + "base_name": "a", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 2, + "dirs_count": 0, + "size_count": 1053674, + "scan_errors": [] + }, + { + "path": "test/a/acegi-security-0.51.jar", + "type": "file", + "name": "acegi-security-0.51.jar", + "base_name": "acegi-security-0.51", + "extension": ".jar", + "size": 176954, + "date": "2020-06-19", + "sha1": "ede156692b33872f5ee9465b7a06d6b2bc9e5e7f", + "md5": "19dad3908042b2bdc50cbfdaed7da200", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/a/dojoz-0.4.1-1.jar", + "type": "file", + "name": "dojoz-0.4.1-1.jar", + "base_name": "dojoz-0.4.1-1", + "extension": ".jar", + "size": 876720, + "date": "2020-06-19", + "sha1": "ae9d68fd6a29906606c2d9407d1cc0749ef84588", + "md5": "508361a1c6273a4c2b8e4945618b509f", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/b", + "type": "directory", + "name": "b", + "base_name": "b", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 1, + "dirs_count": 0, + "size_count": 689791, + "scan_errors": [] + }, + { + "path": "test/b/abbot-0.12.3.jar", + "type": "file", + "name": "abbot-0.12.3.jar", + "base_name": "abbot-0.12.3", + "extension": ".jar", + "size": 689791, + "date": "2020-05-27", + "sha1": "51d28a27d919ce8690a40f4f335b9d591ceb16e9", + "md5": "38206e62a54b0489fb6baa4db5a06093", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/models/match-test-approximate-directory-structure-results.json b/matchcode/tests/matchcode/testfiles/models/match-test-approximate-directory-structure-results.json new file mode 100644 index 00000000..4e1b2981 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/models/match-test-approximate-directory-structure-results.json @@ -0,0 +1,193 @@ +{ + "files": [ + { + "path": "test", + "type": "directory", + "name": "test", + "base_name": "test", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 4, + "dirs_count": 2, + "size_count": 1743469, + "scan_errors": [] + }, + { + "path": "test/c", + "type": "file", + "name": "c", + "base_name": "c", + "extension": "", + "size": 4, + "date": "2020-06-19", + "sha1": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", + "md5": "098f6bcd4621d373cade4e832627b4f6", + "mime_type": "text/plain", + "file_type": "ASCII text, with no line terminators", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/a", + "type": "directory", + "name": "a", + "base_name": "a", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 2, + "dirs_count": 0, + "size_count": 1053674, + "scan_errors": [] + }, + { + "path": "test/a/acegi-security-0.51.jar", + "type": "file", + "name": "acegi-security-0.51.jar", + "base_name": "acegi-security-0.51", + "extension": ".jar", + "size": 176954, + "date": "2020-06-19", + "sha1": "ede156692b33872f5ee9465b7a06d6b2bc9e5e7f", + "md5": "19dad3908042b2bdc50cbfdaed7da200", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/a/dojoz-0.4.1-1.jar", + "type": "file", + "name": "dojoz-0.4.1-1.jar", + "base_name": "dojoz-0.4.1-1", + "extension": ".jar", + "size": 876720, + "date": "2020-06-19", + "sha1": "ae9d68fd6a29906606c2d9407d1cc0749ef84588", + "md5": "508361a1c6273a4c2b8e4945618b509f", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/b", + "type": "directory", + "name": "b", + "base_name": "b", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 1, + "dirs_count": 0, + "size_count": 689791, + "scan_errors": [] + }, + { + "path": "test/b/abbot-0.12.3.jar", + "type": "file", + "name": "abbot-0.12.3.jar", + "base_name": "abbot-0.12.3", + "extension": ".jar", + "size": 689791, + "date": "2020-05-27", + "sha1": "51d28a27d919ce8690a40f4f335b9d591ceb16e9", + "md5": "38206e62a54b0489fb6baa4db5a06093", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/models/match-test-exact-file-results.json b/matchcode/tests/matchcode/testfiles/models/match-test-exact-file-results.json new file mode 100644 index 00000000..0e517fe9 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/models/match-test-exact-file-results.json @@ -0,0 +1,187 @@ +{ + "files": [ + { + "path": "test", + "type": "directory", + "name": "test", + "base_name": "test", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [], + "files_count": 4, + "dirs_count": 2, + "size_count": 1743469, + "scan_errors": [] + }, + { + "path": "test/c", + "type": "file", + "name": "c", + "base_name": "c", + "extension": "", + "size": 4, + "date": "2020-06-19", + "sha1": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", + "md5": "098f6bcd4621d373cade4e832627b4f6", + "mime_type": "text/plain", + "file_type": "ASCII text, with no line terminators", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/a", + "type": "directory", + "name": "a", + "base_name": "a", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [], + "files_count": 2, + "dirs_count": 0, + "size_count": 1053674, + "scan_errors": [] + }, + { + "path": "test/a/acegi-security-0.51.jar", + "type": "file", + "name": "acegi-security-0.51.jar", + "base_name": "acegi-security-0.51", + "extension": ".jar", + "size": 176954, + "date": "2020-06-19", + "sha1": "ede156692b33872f5ee9465b7a06d6b2bc9e5e7f", + "md5": "19dad3908042b2bdc50cbfdaed7da200", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/a/dojoz-0.4.1-1.jar", + "type": "file", + "name": "dojoz-0.4.1-1.jar", + "base_name": "dojoz-0.4.1-1", + "extension": ".jar", + "size": 876720, + "date": "2020-06-19", + "sha1": "ae9d68fd6a29906606c2d9407d1cc0749ef84588", + "md5": "508361a1c6273a4c2b8e4945618b509f", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/b", + "type": "directory", + "name": "b", + "base_name": "b", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [], + "files_count": 1, + "dirs_count": 0, + "size_count": 689791, + "scan_errors": [] + }, + { + "path": "test/b/abbot-0.12.3.jar", + "type": "file", + "name": "abbot-0.12.3.jar", + "base_name": "abbot-0.12.3", + "extension": ".jar", + "size": 689791, + "date": "2020-05-27", + "sha1": "51d28a27d919ce8690a40f4f335b9d591ceb16e9", + "md5": "38206e62a54b0489fb6baa4db5a06093", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/test@0.01" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/models/match-test-exact-package-results.json b/matchcode/tests/matchcode/testfiles/models/match-test-exact-package-results.json new file mode 100644 index 00000000..a78bf767 --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/models/match-test-exact-package-results.json @@ -0,0 +1,185 @@ +{ + "files": [ + { + "path": "test", + "type": "directory", + "name": "test", + "base_name": "test", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [], + "files_count": 4, + "dirs_count": 2, + "size_count": 1743469, + "scan_errors": [] + }, + { + "path": "test/c", + "type": "file", + "name": "c", + "base_name": "c", + "extension": "", + "size": 4, + "date": "2020-06-19", + "sha1": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", + "md5": "098f6bcd4621d373cade4e832627b4f6", + "mime_type": "text/plain", + "file_type": "ASCII text, with no line terminators", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/a", + "type": "directory", + "name": "a", + "base_name": "a", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [], + "files_count": 2, + "dirs_count": 0, + "size_count": 1053674, + "scan_errors": [] + }, + { + "path": "test/a/acegi-security-0.51.jar", + "type": "file", + "name": "acegi-security-0.51.jar", + "base_name": "acegi-security-0.51", + "extension": ".jar", + "size": 176954, + "date": "2020-06-19", + "sha1": "ede156692b33872f5ee9465b7a06d6b2bc9e5e7f", + "md5": "19dad3908042b2bdc50cbfdaed7da200", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/acegi-security@0.51" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/a/dojoz-0.4.1-1.jar", + "type": "file", + "name": "dojoz-0.4.1-1.jar", + "base_name": "dojoz-0.4.1-1", + "extension": ".jar", + "size": 876720, + "date": "2020-06-19", + "sha1": "ae9d68fd6a29906606c2d9407d1cc0749ef84588", + "md5": "508361a1c6273a4c2b8e4945618b509f", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/dojoz@0.4.1-1" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/b", + "type": "directory", + "name": "b", + "base_name": "b", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [], + "files_count": 1, + "dirs_count": 0, + "size_count": 689791, + "scan_errors": [] + }, + { + "path": "test/b/abbot-0.12.3.jar", + "type": "file", + "name": "abbot-0.12.3.jar", + "base_name": "abbot-0.12.3", + "extension": ".jar", + "size": 689791, + "date": "2020-05-27", + "sha1": "51d28a27d919ce8690a40f4f335b9d591ceb16e9", + "md5": "38206e62a54b0489fb6baa4db5a06093", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "matched_to": [ + "pkg:maven/abbot@0.12.3" + ], + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/matchcode/tests/matchcode/testfiles/models/match-test.json b/matchcode/tests/matchcode/testfiles/models/match-test.json new file mode 100644 index 00000000..ef8fa09b --- /dev/null +++ b/matchcode/tests/matchcode/testfiles/models/match-test.json @@ -0,0 +1,194 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "3.1.1.post554.72bef69a3", + "options": { + "input": [ + "/home/jono/Desktop/test" + ], + "--info": true, + "--json-pp": "/home/jono/Desktop/match-test.json" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2020-06-19T230151.719124", + "end_timestamp": "2020-06-19T230151.855330", + "duration": 0.13622713088989258, + "message": null, + "errors": [], + "extra_data": { + "files_count": 4 + } + } + ], + "files": [ + { + "path": "test", + "type": "directory", + "name": "test", + "base_name": "test", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 4, + "dirs_count": 2, + "size_count": 1743469, + "scan_errors": [] + }, + { + "path": "test/c", + "type": "file", + "name": "c", + "base_name": "c", + "extension": "", + "size": 4, + "date": "2020-06-19", + "sha1": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", + "md5": "098f6bcd4621d373cade4e832627b4f6", + "mime_type": "text/plain", + "file_type": "ASCII text, with no line terminators", + "programming_language": null, + "is_binary": false, + "is_text": true, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/a", + "type": "directory", + "name": "a", + "base_name": "a", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 2, + "dirs_count": 0, + "size_count": 1053674, + "scan_errors": [] + }, + { + "path": "test/a/acegi-security-0.51.jar", + "type": "file", + "name": "acegi-security-0.51.jar", + "base_name": "acegi-security-0.51", + "extension": ".jar", + "size": 176954, + "date": "2020-06-19", + "sha1": "ede156692b33872f5ee9465b7a06d6b2bc9e5e7f", + "md5": "19dad3908042b2bdc50cbfdaed7da200", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/a/dojoz-0.4.1-1.jar", + "type": "file", + "name": "dojoz-0.4.1-1.jar", + "base_name": "dojoz-0.4.1-1", + "extension": ".jar", + "size": 876720, + "date": "2020-06-19", + "sha1": "ae9d68fd6a29906606c2d9407d1cc0749ef84588", + "md5": "508361a1c6273a4c2b8e4945618b509f", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + }, + { + "path": "test/b", + "type": "directory", + "name": "b", + "base_name": "b", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 1, + "dirs_count": 0, + "size_count": 689791, + "scan_errors": [] + }, + { + "path": "test/b/abbot-0.12.3.jar", + "type": "file", + "name": "abbot-0.12.3.jar", + "base_name": "abbot-0.12.3", + "extension": ".jar", + "size": 689791, + "date": "2020-05-27", + "sha1": "51d28a27d919ce8690a40f4f335b9d591ceb16e9", + "md5": "38206e62a54b0489fb6baa4db5a06093", + "mime_type": "application/zip", + "file_type": "Zip archive data, at least v1.0 to extract", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": true, + "is_media": false, + "is_source": false, + "is_script": false, + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} diff --git a/matchcode/tests/test_skeleton_codestyle.py b/matchcode/tests/test_skeleton_codestyle.py new file mode 100644 index 00000000..2eb6e558 --- /dev/null +++ b/matchcode/tests/test_skeleton_codestyle.py @@ -0,0 +1,36 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# ScanCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/skeleton for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +import subprocess +import unittest +import configparser + + +class BaseTests(unittest.TestCase): + def test_skeleton_codestyle(self): + """ + This test shouldn't run in proliferated repositories. + """ + setup_cfg = configparser.ConfigParser() + setup_cfg.read("setup.cfg") + if setup_cfg["metadata"]["name"] != "skeleton": + return + + args = "venv/bin/black --check -l 100 setup.py etc tests" + try: + subprocess.check_output(args.split()) + except subprocess.CalledProcessError as e: + print("===========================================================") + print(e.output) + print("===========================================================") + raise Exception( + "Black style check failed; please format the code using:\n" + " python -m black -l 100 setup.py etc tests", + e.output, + ) from e diff --git a/packagedb/src/packagedbio/wsgi.py b/packagedb/src/packagedbio/wsgi.py index c386bb6f..c790c78e 100644 --- a/packagedb/src/packagedbio/wsgi.py +++ b/packagedb/src/packagedbio/wsgi.py @@ -18,6 +18,6 @@ """ -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'packagedbio.settings.production') +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'packagedbio.settings') application = get_wsgi_application() From ce8cd4b47ebed3e197c7015bb036531d45487c9b Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Mon, 5 Dec 2022 20:57:30 +0000 Subject: [PATCH 02/38] Update file headers for Matchcode files * Add apache-2.0 license text to Matchcode Signed-off-by: Jono Yang --- matchcode/LICENSE | 2 - matchcode/apache-2.0.LICENSE | 201 ++++++++++++++++++ matchcode/manage.py | 7 +- matchcode/src/matchcode/api.py | 10 +- matchcode/src/matchcode/api_custom.py | 7 +- matchcode/src/matchcode/fingerprinting.py | 7 +- matchcode/src/matchcode/halohash.py | 27 +-- matchcode/src/matchcode/hash.py | 27 +-- matchcode/src/matchcode/indexing.py | 7 +- .../matchcode/management/commands/__init__.py | 7 +- .../management/commands/index_packages.py | 7 +- .../management/commands/match_scan.py | 7 +- matchcode/src/matchcode/match.py | 7 +- matchcode/src/matchcode/models.py | 7 +- matchcode/src/matchcode/utils.py | 7 +- matchcode/src/matchcodeio/urls.py | 7 +- matchcode/src/matchcodeio/wsgi.py | 7 +- matchcode/tests/matchcode/test_api.py | 7 +- .../tests/matchcode/test_fingerprinting.py | 8 +- .../tests/matchcode/test_index_packages.py | 7 +- matchcode/tests/matchcode/test_match.py | 7 +- matchcode/tests/matchcode/test_models.py | 7 +- packagedb/src/packagedb/models.py | 2 - 23 files changed, 322 insertions(+), 67 deletions(-) delete mode 100644 matchcode/LICENSE create mode 100644 matchcode/apache-2.0.LICENSE diff --git a/matchcode/LICENSE b/matchcode/LICENSE deleted file mode 100644 index 441ebbe0..00000000 --- a/matchcode/LICENSE +++ /dev/null @@ -1,2 +0,0 @@ -Confidential and Proprietary nexB Inc. -Copyright (c) nexB Inc. All rights reserved. \ No newline at end of file diff --git a/matchcode/apache-2.0.LICENSE b/matchcode/apache-2.0.LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/matchcode/apache-2.0.LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/matchcode/manage.py b/matchcode/manage.py index 847e3465..88d9004d 100644 --- a/matchcode/manage.py +++ b/matchcode/manage.py @@ -1,6 +1,11 @@ #!/usr/bin/env python # -# Copyright (c) nexB Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # import os diff --git a/matchcode/src/matchcode/api.py b/matchcode/src/matchcode/api.py index d91da60c..f8496a23 100644 --- a/matchcode/src/matchcode/api.py +++ b/matchcode/src/matchcode/api.py @@ -1,5 +1,10 @@ # -# Copyright (c) nexB Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # from django.db.models import Q @@ -80,7 +85,6 @@ class CharMultipleWidget(widgets.TextInput): """ Enables the support for `MultiValueDict` `?field=a&field=b` reusing the `SelectMultiple.value_from_datadict()` but render as a `TextInput`. - Taken from https://github.com/nexB/dejacode/blob/80fded5521f682b554621a8781fec5560d0c1f09/dje/filters.py#L310 """ def value_from_datadict(self, data, files, name): value = widgets.SelectMultiple().value_from_datadict(data, files, name) @@ -99,7 +103,6 @@ def format_value(self, value): class MultipleCharField(MultipleChoiceField): """ Overrides `MultipleChoiceField` to fit in `MultipleCharFilter`. - Taken from https://github.com/nexB/dejacode/blob/80fded5521f682b554621a8781fec5560d0c1f09/dje/filters.py#L330 """ widget = CharMultipleWidget @@ -110,7 +113,6 @@ def valid_value(self, value): class MultipleCharFilter(MultipleChoiceFilter): """ Filters on multiple values for a CharField type using `?field=a&field=b` URL syntax. - Taken from https://github.com/nexB/dejacode/blob/80fded5521f682b554621a8781fec5560d0c1f09/dje/filters.py#L340 """ field_class = MultipleCharField diff --git a/matchcode/src/matchcode/api_custom.py b/matchcode/src/matchcode/api_custom.py index 30b68ff2..5686311f 100644 --- a/matchcode/src/matchcode/api_custom.py +++ b/matchcode/src/matchcode/api_custom.py @@ -1,5 +1,10 @@ # -# Copyright (c) nexB Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # from rest_framework.pagination import PageNumberPagination diff --git a/matchcode/src/matchcode/fingerprinting.py b/matchcode/src/matchcode/fingerprinting.py index 6d8f2795..7192ee7e 100644 --- a/matchcode/src/matchcode/fingerprinting.py +++ b/matchcode/src/matchcode/fingerprinting.py @@ -1,5 +1,10 @@ # -# Copyright (c) nexB, Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # from matchcode.halohash import BitAverageHaloHash diff --git a/matchcode/src/matchcode/halohash.py b/matchcode/src/matchcode/halohash.py index 0fcb0d52..371be7cf 100644 --- a/matchcode/src/matchcode/halohash.py +++ b/matchcode/src/matchcode/halohash.py @@ -1,26 +1,11 @@ # -# Copyright (c) 2017 nexB Inc. and others. All rights reserved. -# http://nexb.com and https://github.com/nexB/scancode-toolkit/ -# The ScanCode software is licensed under the Apache License version 2.0. -# Data generated with ScanCode require an acknowledgment. -# ScanCode is a trademark of nexB Inc. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # -# You may not use this software except in compliance with the License. -# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -# -# When you publish or redistribute any data created with ScanCode or any ScanCode -# derivative work, you must accompany this data with the following acknowledgment: -# -# Generated with ScanCode and provided on an "AS IS" BASIS, WITHOUT WARRANTIES -# OR CONDITIONS OF ANY KIND, either express or implied. No content created from -# ScanCode should be considered or used as legal advice. Consult an Attorney -# for any legal advice. -# ScanCode is a free software code scanning tool from nexB Inc. and others. -# Visit https://github.com/nexB/scancode-toolkit/ for support and download. import binascii diff --git a/matchcode/src/matchcode/hash.py b/matchcode/src/matchcode/hash.py index 24c1429c..fbed83b0 100644 --- a/matchcode/src/matchcode/hash.py +++ b/matchcode/src/matchcode/hash.py @@ -1,26 +1,11 @@ # -# Copyright (c) 2017 nexB Inc. and others. All rights reserved. -# http://nexb.com and https://github.com/nexB/scancode-toolkit/ -# The ScanCode software is licensed under the Apache License version 2.0. -# Data generated with ScanCode require an acknowledgment. -# ScanCode is a trademark of nexB Inc. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # -# You may not use this software except in compliance with the License. -# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -# -# When you publish or redistribute any data created with ScanCode or any ScanCode -# derivative work, you must accompany this data with the following acknowledgment: -# -# Generated with ScanCode and provided on an "AS IS" BASIS, WITHOUT WARRANTIES -# OR CONDITIONS OF ANY KIND, either express or implied. No content created from -# ScanCode should be considered or used as legal advice. Consult an Attorney -# for any legal advice. -# ScanCode is a free software code scanning tool from nexB Inc. and others. -# Visit https://github.com/nexB/scancode-toolkit/ for support and download. # From https://github.com/nexB/scancode-toolkit-contrib diff --git a/matchcode/src/matchcode/indexing.py b/matchcode/src/matchcode/indexing.py index 9d6ead31..3526da7f 100644 --- a/matchcode/src/matchcode/indexing.py +++ b/matchcode/src/matchcode/indexing.py @@ -1,5 +1,10 @@ # -# Copyright (c) 2020 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # import logging diff --git a/matchcode/src/matchcode/management/commands/__init__.py b/matchcode/src/matchcode/management/commands/__init__.py index d4ee6844..d0fa2322 100644 --- a/matchcode/src/matchcode/management/commands/__init__.py +++ b/matchcode/src/matchcode/management/commands/__init__.py @@ -1,5 +1,10 @@ # -# Copyright (c) 2017 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # import logging diff --git a/matchcode/src/matchcode/management/commands/index_packages.py b/matchcode/src/matchcode/management/commands/index_packages.py index 88b07d1e..cebb2345 100644 --- a/matchcode/src/matchcode/management/commands/index_packages.py +++ b/matchcode/src/matchcode/management/commands/index_packages.py @@ -1,5 +1,10 @@ # -# Copyright (c) 2020 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # from datetime import datetime diff --git a/matchcode/src/matchcode/management/commands/match_scan.py b/matchcode/src/matchcode/management/commands/match_scan.py index e0ca2558..68122ac0 100644 --- a/matchcode/src/matchcode/management/commands/match_scan.py +++ b/matchcode/src/matchcode/management/commands/match_scan.py @@ -1,5 +1,10 @@ # -# Copyright (c) 2016 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # import json diff --git a/matchcode/src/matchcode/match.py b/matchcode/src/matchcode/match.py index ab3a2df9..a0db2faa 100644 --- a/matchcode/src/matchcode/match.py +++ b/matchcode/src/matchcode/match.py @@ -1,5 +1,10 @@ # -# Copyright (c) 2020 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # from functools import reduce diff --git a/matchcode/src/matchcode/models.py b/matchcode/src/matchcode/models.py index 690a85da..07761123 100644 --- a/matchcode/src/matchcode/models.py +++ b/matchcode/src/matchcode/models.py @@ -1,5 +1,10 @@ # -# Copyright (c) 2020 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # from collections import defaultdict diff --git a/matchcode/src/matchcode/utils.py b/matchcode/src/matchcode/utils.py index 72a41646..15dd92f6 100644 --- a/matchcode/src/matchcode/utils.py +++ b/matchcode/src/matchcode/utils.py @@ -1,5 +1,10 @@ # -# Copyright (c) 2020 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # from os import getenv diff --git a/matchcode/src/matchcodeio/urls.py b/matchcode/src/matchcodeio/urls.py index d4c04c82..83b3db2b 100644 --- a/matchcode/src/matchcodeio/urls.py +++ b/matchcode/src/matchcodeio/urls.py @@ -1,5 +1,10 @@ # -# Copyright (c) 2016 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # from django.conf.urls import include diff --git a/matchcode/src/matchcodeio/wsgi.py b/matchcode/src/matchcodeio/wsgi.py index ade3f636..51b91d61 100644 --- a/matchcode/src/matchcodeio/wsgi.py +++ b/matchcode/src/matchcodeio/wsgi.py @@ -1,5 +1,10 @@ # -# Copyright (c) nexB, Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # import os diff --git a/matchcode/tests/matchcode/test_api.py b/matchcode/tests/matchcode/test_api.py index 7eeb3e64..316bd236 100644 --- a/matchcode/tests/matchcode/test_api.py +++ b/matchcode/tests/matchcode/test_api.py @@ -1,5 +1,10 @@ # -# Copyright (c) nexB Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # import os diff --git a/matchcode/tests/matchcode/test_fingerprinting.py b/matchcode/tests/matchcode/test_fingerprinting.py index 12d97d67..03ed656f 100644 --- a/matchcode/tests/matchcode/test_fingerprinting.py +++ b/matchcode/tests/matchcode/test_fingerprinting.py @@ -1,6 +1,12 @@ # -# Copyright (c) nexB Inc. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # + import os from commoncode.resource import VirtualCodebase diff --git a/matchcode/tests/matchcode/test_index_packages.py b/matchcode/tests/matchcode/test_index_packages.py index 1a777a43..87528ade 100644 --- a/matchcode/tests/matchcode/test_index_packages.py +++ b/matchcode/tests/matchcode/test_index_packages.py @@ -1,5 +1,10 @@ # -# Copyright (c) 2020 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # import os diff --git a/matchcode/tests/matchcode/test_match.py b/matchcode/tests/matchcode/test_match.py index 2fe19b75..767ec88e 100644 --- a/matchcode/tests/matchcode/test_match.py +++ b/matchcode/tests/matchcode/test_match.py @@ -1,5 +1,10 @@ # -# Copyright (c) 2017 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # import os diff --git a/matchcode/tests/matchcode/test_models.py b/matchcode/tests/matchcode/test_models.py index 91f279e8..561a8ed1 100644 --- a/matchcode/tests/matchcode/test_models.py +++ b/matchcode/tests/matchcode/test_models.py @@ -1,5 +1,10 @@ # -# Copyright (c) 2017 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. # import os diff --git a/packagedb/src/packagedb/models.py b/packagedb/src/packagedb/models.py index e62481ae..aacf2859 100644 --- a/packagedb/src/packagedb/models.py +++ b/packagedb/src/packagedb/models.py @@ -487,8 +487,6 @@ def get_latest_version(self): return sorted_versions[-1] -# TODO: Sync with DejaCode and insure that DejaCode and MineCode use the same definition -# and same case for everything. We will need to check organization.models.Owner's OWNER_TYPE_CHOICES party_person = 'person' # often loosely defined party_project = 'project' From c1be9563b7210779757ed2c4f6e3dfa7414c50c7 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Mon, 5 Dec 2022 21:25:55 +0000 Subject: [PATCH 03/38] Add clearindex to minecode * Add clearcode as a dependency to minecode * Update configure to install clearcode-toolkit Signed-off-by: Jono Yang --- minecode/configure | 8 +- minecode/setup.cfg | 3 +- minecode/src/clearindex/__init__.py | 0 minecode/src/clearindex/harvest.py | 220 +++++++++++ .../src/clearindex/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/run_clearindex.py | 349 ++++++++++++++++++ minecode/src/clearindex/utils.py | 92 +++++ minecode/src/minecodeio/settings/base.py | 2 + 9 files changed, 670 insertions(+), 4 deletions(-) create mode 100644 minecode/src/clearindex/__init__.py create mode 100644 minecode/src/clearindex/harvest.py create mode 100644 minecode/src/clearindex/management/__init__.py create mode 100644 minecode/src/clearindex/management/commands/__init__.py create mode 100644 minecode/src/clearindex/management/commands/run_clearindex.py create mode 100644 minecode/src/clearindex/utils.py diff --git a/minecode/configure b/minecode/configure index b524a863..2dd7b18a 100755 --- a/minecode/configure +++ b/minecode/configure @@ -27,10 +27,12 @@ CLI_ARGS=$1 # Defaults. Change these variables to customize this script ################################ +CUSTOM_PACKAGES="https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip https://github.com/nexB/clearcode-toolkit/archive/e073e95878b375ec92d817a62c65d963408ff248.zip" + # Requirement arguments passed to pip and used by default or with --dev. -REQUIREMENTS="--editable ../packagedb --editable . --constraint requirements.txt https://github.com/nexB/scancode-toolkit/archive/817161527c864aff937a235856ba9f4d40445c8a.zip" -DEV_REQUIREMENTS="--editable ../packagedb[testing] --editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt https://github.com/nexB/scancode-toolkit/archive/817161527c864aff937a235856ba9f4d40445c8a.zip" -DOCS_REQUIREMENTS="--editable ../packagedb[docs] --editable .[docs] --constraint requirements.txt https://github.com/nexB/scancode-toolkit/archive/817161527c864aff937a235856ba9f4d40445c8a.zip" +REQUIREMENTS="--editable ../packagedb --editable . --constraint requirements.txt $CUSTOM_PACKAGES" +DEV_REQUIREMENTS="--editable ../packagedb[testing] --editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt $CUSTOM_PACKAGES" +DOCS_REQUIREMENTS="--editable ../packagedb[docs] --editable .[docs] --constraint requirements.txt $CUSTOM_PACKAGES" # where we create a virtualenv VIRTUALENV_DIR=venv diff --git a/minecode/setup.cfg b/minecode/setup.cfg index 5dc68b71..49ea8bfc 100644 --- a/minecode/setup.cfg +++ b/minecode/setup.cfg @@ -41,13 +41,14 @@ include_package_data = true zip_safe = false install_requires = arrow==1.2.3 + clearcode==0.0.3 debian-inspector==31.0.0 Django==4.1.2 django-filter==22.1 djangorestframework==3.14.0 ftputil==5.0.4 jawa==2.2.0 - packagedb + packagedb==2.0.0 psycopg2-binary==2.9.3 PyGithub==1.56 rubymarshal==1.0.3 diff --git a/minecode/src/clearindex/__init__.py b/minecode/src/clearindex/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/minecode/src/clearindex/harvest.py b/minecode/src/clearindex/harvest.py new file mode 100644 index 00000000..ef56f6b3 --- /dev/null +++ b/minecode/src/clearindex/harvest.py @@ -0,0 +1,220 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + +import logging +import sys + +from django.db import IntegrityError +from django.db import transaction +from django.utils import timezone + +from clearcode.models import CDitem +from packagedb.models import Package +from packagedb.models import Resource + +from discovery.management.commands.run_map import merge_packages +from discovery.utils import stringify_null_purl_fields + + +logger = logging.getLogger(__name__) +logging.basicConfig(stream=sys.stdout) +logger.setLevel(logging.INFO) + + +def get_resource_license_expressions(file_data): + """ + Return a string that contains all the license_expression statements (deduped), + with a newline separating each or None if there are no license_expression statements + in the scan data. + """ + license_expressions = file_data.get('license_expressions', []) or [] + if license_expressions == []: + return + + expressions = set(list(expression for expression in license_expressions)) + + return '\n'.join(expressions) + + +def get_resource_copyright_statements(file_data): + """ + Return a string that contains all the copyright statements (deduped), with a newline + separating each or None if there are no copyright statements in the scan data. + """ + copyrights = file_data.get('copyrights', []) or [] + if copyrights == []: + return + + statements = set(list(copyright.get('value') for copyright in copyrights)) + + return '\n'.join(statements) + + +def create_from_harvest(package_scan={}, files_data=[], cditem_path=''): + """ + Return a Package object, created or updated via a ScanCode-Toolkit "package" scan. + """ + fields = ( + 'type', + 'namespace', + 'name', + 'version', + 'qualifiers', + 'subpath', + 'primary_language', + 'description', + 'keywords', + 'homepage_url', + 'download_url', + 'size', + 'sha1', + 'md5', + 'sha256', + 'sha512', + 'bug_tracking_url', + 'code_view_url', + 'vcs_url', + 'copyright', + 'license_expression', + 'declared_license', + 'notice_text', + 'source_packages', + ) + + package_data = {field_name: package_scan.get(field_name) for field_name in fields} + + stringify_null_purl_fields(package_data) + + pkg_type = package_data.get('type') + namespace = package_data.get('namespace') + name = package_data.get('name') + version = package_data.get('version') + qualifiers = package_data.get('qualifiers') + subpath = package_data.get('subpath') + + download_url = package_data.get('download_url') + if not download_url: + logger.error('Null `download_url` value for `package_data`: {}'.format(package_data)) + return + + # This ugly block is needed until https://github.com/nexB/packagedb/issues/14 + # is complete. + try: + package = Package.objects.get( + type=pkg_type, + namespace=namespace, + name=name, + version=version, + qualifiers=qualifiers, + subpath=subpath, + download_url=download_url + ) + # Merge package records if it already exists + merge_packages( + existing_package=package, + new_package_data=package_data, + replace=False + ) + package.append_to_history('Updated package from CDitem harvest: {}'.format(cditem_path)) + + logger.info('Merged package data from scancode harvest: {}'.format(package)) + + except Package.DoesNotExist: + try: + package = Package.objects.get(download_url=download_url) + # Merge package records if it already exists + merge_packages( + existing_package=package, + new_package_data=package_data, + replace=False + ) + package.append_to_history('Updated package from CDitem harvest: {}'.format(cditem_path)) + + logger.info('Merged package data from scancode harvest: {}'.format(package)) + + except Package.DoesNotExist: + package = Package.objects.create(**package_data) + package.append_to_history('Created package from CDitem harvest: {}'.format(cditem_path)) + + logger.info('Created package from scancode harvest: {}'.format(package)) + + # Now, add resources to the Package. + for f in files_data: + path = f.get('path') + is_file = f.get('type', '') == 'file' + copyright = get_resource_copyright_statements(f) + license_expression = get_resource_license_expressions(f) + file_data = dict( + package=package, + path=path, + size=f.get('size'), + sha1=f.get('sha1'), + md5=f.get('md5'), + sha256=f.get('sha256'), + git_sha1=f.get('git_sha1'), + is_file=is_file, + copyright=copyright, + license_expression=license_expression, + ) + + # Ensure there will be no `path` collision + try: + Resource.objects.get(package=package, path=path) + except Resource.DoesNotExist: + Resource.objects.create(**file_data) + + return package + + +def map_scancode_harvest(cditem): + """ + Return the number of created or merged Packages from a scancode harvest and create + its Resources. + """ + with transaction.atomic(): + try: + harvest_data = cditem.data + except ValueError: + err_msg = 'CDitemError: empty content field for CDitem: {}'.format(cditem.path) + logger.error(err_msg) + + cditem.map_error = err_msg + cditem.save() + return 0 + + content = harvest_data.get('content', {}) or {} + files_data = content.get('files', []) or [] + summary = content.get('summary', {}) or {} + packages = summary.get('packages', []) or [] + + for package_scan in packages: + # Check if there is a valid download url. Missing download_url values are + # considered map_errors, as a Package object cannot have a `Null` + # download_url value. + download_url = package_scan.get('download_url') + if not download_url: + err_msg = 'CDitemError: empty download_url for package_scan: {}'.format(package_scan) + logger.error(err_msg) + + cditem.map_error = err_msg + cditem.save() + continue + + # Package + Resource creation + # pass the `path` of the CDitem for logging purposes + create_from_harvest(package_scan, files_data, cditem.path) + + cditem.last_map_date = timezone.now() + cditem.save() + + return len(packages) diff --git a/minecode/src/clearindex/management/__init__.py b/minecode/src/clearindex/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/minecode/src/clearindex/management/commands/__init__.py b/minecode/src/clearindex/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/minecode/src/clearindex/management/commands/run_clearindex.py b/minecode/src/clearindex/management/commands/run_clearindex.py new file mode 100644 index 00000000..9455d83c --- /dev/null +++ b/minecode/src/clearindex/management/commands/run_clearindex.py @@ -0,0 +1,349 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +from __future__ import absolute_import +from __future__ import unicode_literals +from __future__ import print_function + +import logging +import signal +import sys +import time + +from django.core.exceptions import ObjectDoesNotExist +from django.db import transaction +from django.db.utils import OperationalError +from django.utils import timezone + +from clearcode.models import CDitem +from clearindex.utils import get_error_message +from clearindex.utils import VerboseCommand +from discovery.management.commands.run_map import merge_packages +from discovery.utils import stringify_null_purl_fields + +from packagedb.models import Package +from packagedcode import licensing +from packagedcode import maven +from packagedcode import npm +from packagedcode import nuget +from packagedcode import pypi +from packagedcode import rubygems +from packagedcode.models import Package as ScannedPackage + + +from clearindex import harvest +from clearindex.utils import get_error_message +from clearindex.utils import VerboseCommand + +TRACE = False + +logger = logging.getLogger(__name__) +logging.basicConfig(stream=sys.stdout) +logger.setLevel(logging.INFO) + + +# sleep duration in seconds when the queue is empty +SLEEP_WHEN_EMPTY = 10 + +MUST_STOP = False + + +def stop_handler(*args, **kwargs): + """ + Signal handler to set global variable to True. + """ + global MUST_STOP + MUST_STOP = True + + +signal.signal(signal.SIGTERM, stop_handler) + +# number of mappable CDItem processed at once +MAP_BATCH_SIZE = 10 + + +PACKAGE_TYPES_BY_CD_TYPE = { + 'crate': 'cargo', + 'deb': 'deb', + 'debsrc': 'deb', + # Currently used only for maven packages + 'sourcearchive': 'maven', + 'maven': 'maven', + 'composer': 'composer', + # Currently used only for Github repo/packages + 'git': 'github', + 'pod': 'pod', + 'nuget': 'nuget', + 'pypi': 'pypi', + 'gem': 'gem', +} + + +# TODO: Update with more Package types when scancode-toolkit is updated +PACKAGE_TYPES_WITH_GET_URLS = { + 'maven': maven.get_urls, + 'npm': npm.get_urls, + 'pypi': pypi.get_pypi_urls, + 'gem': rubygems.get_urls, + 'nuget': nuget.get_urls, +} + + +class Command(VerboseCommand): + help = 'Run a mapping worker.' + + def add_arguments(self, parser): + parser.add_argument( + '--exit-on-empty', + dest='exit_on_empty', + default=False, + action='store_true', + help='Do not loop forever. Exit when the queue is empty.') + + def handle(self, *args, **options): + """ + Get the next available CDitem and start the processing. + Loops forever and sleeps a short while if there are no CDitem left to map. + """ + global MUST_STOP + + logger.setLevel(self.get_verbosity(**options)) + exit_on_empty = options.get('exit_on_empty') + + sleeping = False + created_packages_count = 0 + + logger.info('Running ClearIndex') + while True: + if MUST_STOP: + logger.info('Graceful exit of the map loop.') + break + + mappable_definitions = CDitem.objects.mappable_definitions()[:MAP_BATCH_SIZE] + mappable_scancode_harvests = CDitem.objects.mappable_scancode_harvests()[:MAP_BATCH_SIZE] + + try: + if not mappable_definitions and not mappable_scancode_harvests: + if exit_on_empty: + logger.info('No mappable CDitem, exiting...') + break + + # Only log a single message when we go to sleep + if not sleeping: + sleeping = True + logger.info('No mappable CDitem, sleeping...') + + time.sleep(SLEEP_WHEN_EMPTY) + continue + + sleeping = False + + for cditem in mappable_definitions: + package = map_definition(cditem) + if not package: + continue + created_packages_count += 1 + + for cditem in mappable_scancode_harvests: + # scancode harvests may contain multiple package entries + package_count = harvest.map_scancode_harvest(cditem) + if isinstance(package_count, int): + created_packages_count += package_count + + except OperationalError as e: + logger.error(e) + break + + msg = '{}: {} Packages processed.' + msg = msg.format(timezone.now(), created_packages_count) + logger.info(msg) + + +def map_definition(cditem): + """ + Map a CD definition. Return the Package created from a mapped CD definition + or None if a Package could not be created or an Exception has occured. + """ + try: + with transaction.atomic(): + # We create a new Package from a definition, if it does not exist in the PackageDB + package = get_or_create_package_from_cditem_definition(cditem) + if not package: + return + package.last_modified_date = timezone.now() + package.save() + cditem.last_map_date = timezone.now() + cditem.save() + return package + except Exception as e: + msg = 'Error: Failed to map while processing CDitem: {}\n'.format( + repr(cditem.path)) + msg += get_error_message(e) + logger.error(msg) + cditem.map_error = msg + cditem.save() + + +def get_coords_des_and_lic_from_def(definition): + return definition.get('coordinates', {}), definition.get('described', {}), definition.get('licensed', {}) + + +#CD_TYPES_WITH_SOURCE = ('debsrc', 'npm', 'sourcearchive',) + + +def get_or_create_package_from_cditem_definition(cditem): + """ + Create a Package from a CDitem definition or return a Package if it already exists + """ + definition = cditem.data + if not definition: + raise Exception('No data available for this definition') + coordinates, described, licensed = get_coords_des_and_lic_from_def(definition) + + download_url = described.get('urls', {}).get('download', '') + if not download_url: + # We use our data to create a Package in order to form the download_url, since we do not have the download_url for the Package + # We need to have a unique download URL for every Package + download_url = create_download_url_from_coords(coordinates) + if not download_url: + raise Exception('No download URL is available for this definition') + + if download_url.startswith('http://central.maven.org'): + split_download_url = download_url.rsplit('http://central.maven.org') + if len(split_download_url) == 2: + download_url = 'https://repo1.maven.org' + split_download_url[1] + + stringify_null_purl_fields(coordinates) + + namespace = coordinates.get('namespace') + namespace = namespace if namespace != '-' else '' + name = coordinates.get('name') + version = coordinates.get('revision') + package_type = coordinates.get('type') + converted_package_type = PACKAGE_TYPES_BY_CD_TYPE.get(package_type) or package_type + # TODO: Source packages need to be updated for clearlydefined, link source packages to binary packages + hashes = described.get('hashes', {}) + sha1 = hashes.get('sha1') + sha256 = hashes.get('sha256') + homepage_url = described.get('projectWebsite') + release_date = described.get('releaseDate') + declared_license = licensed.get('declared') + normalized_license_expression = licensing.get_normalized_expression(declared_license) + copyrights = get_parties_from_licensed(licensed) + copyrights = '\n'.join(copyrights) + definition_mining_level = 0 + + existing_package = None + try: + # FIXME: also consider the Package URL fields!!! + existing_package = Package.objects.get(download_url=download_url) + except ObjectDoesNotExist: + pass + + if not existing_package: + package, created = Package.objects.get_or_create( + type=converted_package_type, + namespace=namespace, + name=name, + version=version, + download_url=download_url, + homepage_url=homepage_url, + sha1=sha1, + sha256=sha256, + release_date=release_date, + declared_license=declared_license, + license_expression=normalized_license_expression, + copyright=copyrights, + mining_level=definition_mining_level + ) + # log history if package was created + if created: + package.append_to_history('Created package from CDitem definition: {}'.format(cditem.path)) + + else: + # TODO: This is temporary until we fold clearindex into minecode mapping + # proper, otherwise we should base this decision off of mining level + if existing_package.mining_level < definition_mining_level: + new_package_data = ScannedPackage( + type=converted_package_type, + namespace=namespace, + name=name, + version=version, + download_url=download_url, + homepage_url=homepage_url, + sha1=sha1, + sha256=sha256, + release_date=release_date, + declared_license=declared_license, + license_expression=normalized_license_expression, + copyright=copyrights, + ).to_dict() + merge_packages( + existing_package=existing_package, + new_package_data=new_package_data, + replace=True + ) + package = existing_package + package.append_to_history('Updated package from CDitem definition: {}'.format(cditem.path)) + + return package + + +def is_scancode_scan(harvest): + return harvest.get('_metadata', {}).get('type', '') == 'scancode' + + +def create_download_url_from_coords(coord): + """ + Return a download URL for a supported Package from Coordinates `coord` + """ + ptype = coord.get('type') + namespace = coord.get('namespace') + name = coord.get('name') + version = coord.get('revision') + + package_type = PACKAGE_TYPES_BY_CD_TYPE.get(ptype) + if not package_type: + raise Exception('Unsupported ClearlyDefined package type: {}'.format(ptype)) + + get_urls = PACKAGE_TYPES_WITH_GET_URLS.get(package_type) + if get_urls: + urls = get_urls(namespace=namespace, name=name, version=version) + return urls['repository_download_url'] + + +def str2coord(s): + """ + Return a mapping of CD coordinates from a `s` CD coordinates, URL or URN + string. + + Some example of the supported input strings are: + URL: "cd:/gem/rubygems/-/mocha/1.7.0" + URN: "urn:gem:rubygems:-:mocha:revision:1.7.0:tool:scancode:3.1.0" + plain: /gem/rubygems/foo/mocha/1.7.0" + """ + from itertools import izip_longest + is_urn = s.startswith('urn') + is_url = s.startswith('cd:') + splitter = ':' if is_urn else '/' + segments = s.strip(splitter).split(splitter) + if is_urn or is_url: + segments = segments[1:] + # ignore extra segments for now beyond the 5 fisrt (such as the PR of a curation) + segments = segments[:5] + + fields = ('type', 'provider', 'namespace', 'name', 'revision',) + return dict(izip_longest(fields, segments)) + + +def get_parties_from_licensed(licensed): + """ + Return a list of Copyright statements from `licensed`, if available + """ + return licensed.get('facets', {}).get('core', {}).get('attribution', {}).get('parties', []) diff --git a/minecode/src/clearindex/utils.py b/minecode/src/clearindex/utils.py new file mode 100644 index 00000000..c21cbea1 --- /dev/null +++ b/minecode/src/clearindex/utils.py @@ -0,0 +1,92 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +from unittest import TestCase +import logging +import ntpath +import os +import posixpath +import traceback + +from django.core.management.base import BaseCommand +from django.test import TestCase as DjangoTestCase + +from discovery.utils_test import JsonBasedTesting + + +class VerboseCommand(BaseCommand): + """ + Base verbosity-aware Command. + Command modules should define logging and subclasses should call + logger.setLevel(self.get_verbosity(**options)) + in their handle() method. + """ + + def get_verbosity(self, **options): + verbosity = int(options.get('verbosity', 1)) + levels = {1: logging.INFO, 2: logging.ERROR, 3: logging.DEBUG} + return levels.get(verbosity, logging.CRITICAL) + + MUST_STOP = False + + @classmethod + def stop_handler(cls, *args, **kwargs): + """ + Signal handler use to support a graceful exit when flag is to True. + Subclasses must create this signal to use this: + signal.signal(signal.SIGTERM, Command.stop_handler) + """ + cls.MUST_STOP = True + + +def get_error_message(e): + """ + Return an error message with a traceback given an exception. + """ + tb = traceback.format_exc() + msg = e.__class__.__name__ + ' ' + repr(e) + msg += '\n' + tb + return msg + + +""" +The conventions used for the tests are: +- for tests that require files these are stored in the testfiles directory +- each test must use its own sub directory in testfiles. The is called the +'base' +- testfiles that are more than a few KB should be in a bzip2 tarball +""" + + +class BaseTestCase(TestCase): + BASE_DIR = os.path.join(os.path.dirname(__file__), 'testfiles') + + @classmethod + def get_test_loc(cls, path): + """ + Given a path relative to the test files directory, return the location + to a test file or directory for this path. No copy is done. + """ + path = to_os_native_path(path) + location = os.path.abspath(os.path.join(cls.BASE_DIR, path)) + return location + + +class ClearIndexTestCase(JsonBasedTesting, BaseTestCase, DjangoTestCase): + databases = '__all__' + + +def to_os_native_path(path): + """ + Normalize a path to use the native OS path separator. + """ + path = path.replace(posixpath.sep, os.path.sep) + path = path.replace(ntpath.sep, os.path.sep) + path = path.rstrip(os.path.sep) + return path diff --git a/minecode/src/minecodeio/settings/base.py b/minecode/src/minecodeio/settings/base.py index af080d1d..d4fb105b 100644 --- a/minecode/src/minecodeio/settings/base.py +++ b/minecode/src/minecodeio/settings/base.py @@ -36,7 +36,9 @@ 'rest_framework', 'django_filters', 'discovery', + 'clearindex', 'packagedb', + 'clearcode', ) MIDDLEWARE = ( From 107d5ae0e74384c64d084459392903ffd40e512f Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Mon, 5 Dec 2022 21:30:05 +0000 Subject: [PATCH 04/38] Use same database for everything in matchcode * Add minecode and clearcode as dependencies for matchcode * Update configure to install clearcode Signed-off-by: Jono Yang --- matchcode/Makefile | 18 +++++++--------- matchcode/configure | 8 +++---- matchcode/setup.cfg | 2 ++ .../src/matchcodeio/settings/__init__.py | 19 +++++------------ .../src/matchcodeio/settings/dbrouter.py | 21 ------------------- 5 files changed, 19 insertions(+), 49 deletions(-) delete mode 100644 matchcode/src/matchcodeio/settings/dbrouter.py diff --git a/matchcode/Makefile b/matchcode/Makefile index cdb5fbe2..6ce4a7de 100644 --- a/matchcode/Makefile +++ b/matchcode/Makefile @@ -18,7 +18,7 @@ GET_SECRET_KEY=`base64 /dev/urandom | head -c50` # Customize with `$ make envfile ENV_FILE=/etc/purldb/.env` ENV_FILE=.env # Customize with `$ make postgres MATCHCODEIO_DB_PASSWORD=YOUR_PASSWORD` -MATCHCODEIO_DB_PASSWORD=matc34-u5er +MATCHCODEIO_DB_PASSWORD=packagedb # Use sudo for postgres, but only on Linux UNAME := $(shell uname) @@ -50,8 +50,6 @@ envfile-ci: envfile @echo "-> Add Azure Pipelines Postgres DB name and password to .env" @echo MATCHCODEIO_DB_USER="postgres" >> ${ENV_FILE} @echo MATCHCODEIO_DB_PASSWORD="postgres" >> ${ENV_FILE} - @echo PACKAGEDB_DB_USER="postgres" >> ${ENV_FILE} - @echo PACKAGEDB_DB_PASSWORD="postgres" >> ${ENV_FILE} isort: @echo "-> Apply isort changes to ensure proper imports ordering" @@ -85,13 +83,13 @@ migrate: postgres: @echo "-> Configure PostgreSQL database" - @echo "-> Create database user 'matchcode'" - ${SUDO_POSTGRES} createuser --no-createrole --no-superuser --login --inherit --createdb matchcode || true - ${SUDO_POSTGRES} psql -c "alter user matchcode with encrypted password '${MATCHCODEIO_DB_PASSWORD}';" || true - @echo "-> Drop 'matchcode' database" - ${SUDO_POSTGRES} dropdb matchcode || true - @echo "-> Create 'matchcode' database" - ${SUDO_POSTGRES} createdb --encoding=utf-8 --owner=matchcode matchcode + @echo "-> Create database user 'packagedb'" + ${SUDO_POSTGRES} createuser --no-createrole --no-superuser --login --inherit --createdb packagedb || true + ${SUDO_POSTGRES} psql -c "alter user packagedb with encrypted password '${MATCHCODEIO_DB_PASSWORD}';" || true + @echo "-> Drop 'packagedb' database" + ${SUDO_POSTGRES} dropdb packagedb || true + @echo "-> Create 'packagedb' database" + ${SUDO_POSTGRES} createdb --encoding=utf-8 --owner=packagedb packagedb @$(MAKE) migrate run: diff --git a/matchcode/configure b/matchcode/configure index e777a5d2..c04c077f 100755 --- a/matchcode/configure +++ b/matchcode/configure @@ -27,12 +27,12 @@ CLI_ARGS=$1 # Defaults. Change these variables to customize this script ################################ -CUSTOM_PACKAGES="https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip" +CUSTOM_PACKAGES="https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip https://github.com/nexB/clearcode-toolkit/archive/e073e95878b375ec92d817a62c65d963408ff248.zip" # Requirement arguments passed to pip and used by default or with --dev. -REQUIREMENTS="--editable . --constraint requirements.txt $CUSTOM_PACKAGES" -DEV_REQUIREMENTS="--editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt $CUSTOM_PACKAGES" -DOCS_REQUIREMENTS="--editable .[docs] --constraint requirements.txt $CUSTOM_PACKAGES" +REQUIREMENTS="--editable ../packagedb --editable ../minecode --editable . --constraint requirements.txt $CUSTOM_PACKAGES" +DEV_REQUIREMENTS="--editable ../packagedb[testing] --editable ../minecode[testing] --editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt $CUSTOM_PACKAGES" +DOCS_REQUIREMENTS="--editable ../packagedb[docs] --editable ../minecode[docs] --editable .[docs] --constraint requirements.txt $CUSTOM_PACKAGES" # where we create a virtualenv VIRTUALENV_DIR=venv diff --git a/matchcode/setup.cfg b/matchcode/setup.cfg index 20ecdec2..03928ee9 100644 --- a/matchcode/setup.cfg +++ b/matchcode/setup.cfg @@ -31,11 +31,13 @@ include_package_data = true zip_safe = false install_requires = bitarray == 2.6.0 + clearcode==0.0.3 commoncode == 31.0.0 Django == 4.1.2 django-environ==0.9.0 djangorestframework == 3.14.0 django-filter == 22.1 + minecode == 2.0.0 packagedb == 2.0.0 psycopg2 == 2.9.3 scancode-toolkit == 31.2.2 diff --git a/matchcode/src/matchcodeio/settings/__init__.py b/matchcode/src/matchcodeio/settings/__init__.py index 01eb05db..bef62831 100644 --- a/matchcode/src/matchcodeio/settings/__init__.py +++ b/matchcode/src/matchcodeio/settings/__init__.py @@ -55,6 +55,8 @@ # Must come before Third-party apps for proper templates override 'matchcode', 'packagedb', + 'clearindex', + 'clearcode', # Django built-in "django.contrib.auth", 'django.contrib.contenttypes', @@ -84,26 +86,15 @@ # Database -DATABASE_ROUTERS = ['matchcodeio.settings.dbrouter.PackagedbRouter'] -DATABASE_APPS_MAPPING = {'packagedb': 'packagedb'} DATABASES = { 'default': { 'ENGINE': env.str('MATCHCODEIO_DB_ENGINE', 'django.db.backends.postgresql'), 'HOST': env.str('MATCHCODEIO_DB_HOST', 'localhost'), - 'NAME': env.str('MATCHCODEIO_DB_NAME', 'matchcode'), - 'USER': env.str('MATCHCODEIO_DB_USER', 'matchcode'), - 'PASSWORD': env.str('MATCHCODEIO_DB_PASSWORD', 'matc34-u5er'), + 'NAME': env.str('MATCHCODEIO_DB_NAME', 'packagedb'), + 'USER': env.str('MATCHCODEIO_DB_USER', 'packagedb'), + 'PASSWORD': env.str('MATCHCODEIO_DB_PASSWORD', 'packagedb'), 'PORT': env.str('MATCHCODEIO_DB_PORT', '5432'), 'ATOMIC_REQUESTS': True, - }, - 'packagedb': { - "ENGINE": env.str("PACKAGEDB_DB_ENGINE", "django.db.backends.postgresql"), - "HOST": env.str("PACKAGEDB_DB_HOST", "localhost"), - "NAME": env.str("PACKAGEDB_DB_NAME", "packagedb"), - "USER": env.str("PACKAGEDB_DB_USER", "packagedb"), - "PASSWORD": env.str("PACKAGEDB_DB_PASSWORD", "packagedb"), - "PORT": env.str("PACKAGEDB_DB_PORT", "5432"), - "ATOMIC_REQUESTS": True, } } diff --git a/matchcode/src/matchcodeio/settings/dbrouter.py b/matchcode/src/matchcodeio/settings/dbrouter.py deleted file mode 100644 index 59a8a0da..00000000 --- a/matchcode/src/matchcodeio/settings/dbrouter.py +++ /dev/null @@ -1,21 +0,0 @@ -class PackagedbRouter(object): - def db_for_read(self, model, **hints): - if model._meta.app_label == 'packagedb': - return 'packagedb' - return None - - def db_for_write(self, model, **hints): - if model._meta.app_label == 'packagedb': - return 'packagedb' - return None - - def allow_relation(self, obj1, obj2, **hints): - if obj1._meta.app_label == 'packagedb' or \ - obj2._meta.app_label == 'packagedb': - return True - return None - - def allow_migrate(self, db, app_label, model_name=None, **hints): - if app_label == 'packagedb': - return db == 'packagedb' - return None From a40124a8316550961affa6018befbd731d1e64d6 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Mon, 5 Dec 2022 23:40:17 +0000 Subject: [PATCH 05/38] Add clearcode-toolkit to purldb Signed-off-by: Jono Yang --- clearcode-toolkit/.gitignore | 58 ++ clearcode-toolkit/CHANGELOG.rst | 10 + clearcode-toolkit/NOTICE | 19 + clearcode-toolkit/README.rst | 189 ++++++ clearcode-toolkit/apache-2.0.LICENSE | 202 ++++++ clearcode-toolkit/configure | 7 + clearcode-toolkit/createdb.sh | 10 + clearcode-toolkit/devops-requirements.in | 1 + clearcode-toolkit/devops-requirements.txt | 14 + .../docs/ClearCode Introduction-June 2020.odp | Bin 0 -> 416290 bytes .../docs/ClearCode Introduction-June 2020.pdf | Bin 0 -> 586265 bytes clearcode-toolkit/etc/ansible/README.rst | 46 ++ clearcode-toolkit/etc/ansible/hosts | 2 + .../etc/ansible/roles/common/tasks/main.yml | 31 + .../etc/ansible/roles/postgres/tasks/main.yml | 77 +++ .../etc/ansible/roles/project/tasks/main.yml | 92 +++ clearcode-toolkit/etc/ansible/site.yml | 31 + clearcode-toolkit/etc/ansible/vars.yml | 35 ++ .../etc/scripts/clearcode-api-backup.py | 201 ++++++ .../etc/scripts/clearcode-api-import.py | 147 +++++ clearcode-toolkit/manage.py | 10 + clearcode-toolkit/requirements.txt | 19 + clearcode-toolkit/setup.cfg | 50 ++ clearcode-toolkit/setup.py | 65 ++ clearcode-toolkit/src/clearcode/__init__.py | 0 clearcode-toolkit/src/clearcode/api.py | 80 +++ clearcode-toolkit/src/clearcode/cdutils.py | 561 +++++++++++++++++ clearcode-toolkit/src/clearcode/dbconf.py | 68 ++ clearcode-toolkit/src/clearcode/dbsettings.py | 115 ++++ clearcode-toolkit/src/clearcode/load.py | 116 ++++ .../src/clearcode/migrations/0001_initial.py | 22 + .../migrations/0002_auto_20200331_1052.py | 23 + .../clearcode/migrations/0003_cditem_uuid.py | 34 + .../src/clearcode/migrations/__init__.py | 0 clearcode-toolkit/src/clearcode/models.py | 139 +++++ clearcode-toolkit/src/clearcode/sync.py | 584 ++++++++++++++++++ .../src/clearcode/tests/__init__.py | 0 .../src/clearcode/tests/test_api.py | 126 ++++ .../src/clearcode/tests/test_models.py | 104 ++++ .../src/clearcode/tests/test_sync.py | 46 ++ matchcode/Makefile | 9 + matchcode/configure | 8 +- matchcode/requirements.txt | 2 +- matchcode/setup.cfg | 9 +- .../{settings/__init__.py => settings.py} | 3 +- minecode/Makefile | 6 + minecode/configure | 6 +- minecode/setup.cfg | 2 +- minecode/src/minecodeio/settings/base.py | 6 +- 49 files changed, 3364 insertions(+), 21 deletions(-) create mode 100644 clearcode-toolkit/.gitignore create mode 100644 clearcode-toolkit/CHANGELOG.rst create mode 100644 clearcode-toolkit/NOTICE create mode 100644 clearcode-toolkit/README.rst create mode 100644 clearcode-toolkit/apache-2.0.LICENSE create mode 100755 clearcode-toolkit/configure create mode 100755 clearcode-toolkit/createdb.sh create mode 100644 clearcode-toolkit/devops-requirements.in create mode 100644 clearcode-toolkit/devops-requirements.txt create mode 100644 clearcode-toolkit/docs/ClearCode Introduction-June 2020.odp create mode 100644 clearcode-toolkit/docs/ClearCode Introduction-June 2020.pdf create mode 100644 clearcode-toolkit/etc/ansible/README.rst create mode 100644 clearcode-toolkit/etc/ansible/hosts create mode 100644 clearcode-toolkit/etc/ansible/roles/common/tasks/main.yml create mode 100644 clearcode-toolkit/etc/ansible/roles/postgres/tasks/main.yml create mode 100644 clearcode-toolkit/etc/ansible/roles/project/tasks/main.yml create mode 100644 clearcode-toolkit/etc/ansible/site.yml create mode 100644 clearcode-toolkit/etc/ansible/vars.yml create mode 100644 clearcode-toolkit/etc/scripts/clearcode-api-backup.py create mode 100644 clearcode-toolkit/etc/scripts/clearcode-api-import.py create mode 100755 clearcode-toolkit/manage.py create mode 100644 clearcode-toolkit/requirements.txt create mode 100644 clearcode-toolkit/setup.cfg create mode 100644 clearcode-toolkit/setup.py create mode 100644 clearcode-toolkit/src/clearcode/__init__.py create mode 100644 clearcode-toolkit/src/clearcode/api.py create mode 100644 clearcode-toolkit/src/clearcode/cdutils.py create mode 100644 clearcode-toolkit/src/clearcode/dbconf.py create mode 100644 clearcode-toolkit/src/clearcode/dbsettings.py create mode 100644 clearcode-toolkit/src/clearcode/load.py create mode 100644 clearcode-toolkit/src/clearcode/migrations/0001_initial.py create mode 100644 clearcode-toolkit/src/clearcode/migrations/0002_auto_20200331_1052.py create mode 100644 clearcode-toolkit/src/clearcode/migrations/0003_cditem_uuid.py create mode 100644 clearcode-toolkit/src/clearcode/migrations/__init__.py create mode 100644 clearcode-toolkit/src/clearcode/models.py create mode 100644 clearcode-toolkit/src/clearcode/sync.py create mode 100644 clearcode-toolkit/src/clearcode/tests/__init__.py create mode 100644 clearcode-toolkit/src/clearcode/tests/test_api.py create mode 100644 clearcode-toolkit/src/clearcode/tests/test_models.py create mode 100644 clearcode-toolkit/src/clearcode/tests/test_sync.py rename matchcode/src/matchcodeio/{settings/__init__.py => settings.py} (99%) diff --git a/clearcode-toolkit/.gitignore b/clearcode-toolkit/.gitignore new file mode 100644 index 00000000..a6088237 --- /dev/null +++ b/clearcode-toolkit/.gitignore @@ -0,0 +1,58 @@ +*.py[cod] + +# virtualenv and other misc bits +*.egg-info +/dist +/build +/bin +/lib +/lib64 +/scripts +/Scripts +/Lib +/pip-selfcheck.json +/tmp +.Python +/include +/Include +/local +*/local/* +pyvenv.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.cache +.coverage +.coverage.* +nosetests.xml +htmlcov + +# Translations +*.mo + +# IDEs +.project +.pydevproject +.idea +org.eclipse.core.resources.prefs +.vscode + +# Sphinx +docs/_build + +.DS_Store +*~ +.*.sw[po] +.build +.ve +*.bak +/.cache/ + +/local/ +/share/ +/tcl/ +/.python-version +/lib64/ +/lib64/ diff --git a/clearcode-toolkit/CHANGELOG.rst b/clearcode-toolkit/CHANGELOG.rst new file mode 100644 index 00000000..16795cfd --- /dev/null +++ b/clearcode-toolkit/CHANGELOG.rst @@ -0,0 +1,10 @@ + +Changelog +========= + + +0.0.3 2020-06-19 +---------------- + +* Initial release + diff --git a/clearcode-toolkit/NOTICE b/clearcode-toolkit/NOTICE new file mode 100644 index 00000000..3bf20fa9 --- /dev/null +++ b/clearcode-toolkit/NOTICE @@ -0,0 +1,19 @@ +Software license +================ + +Copyright (c) nexB Inc. and others. All rights reserved. + +ClearCode is a free software tool from nexB Inc. and others. +Visit https://github.com/nexB/clearcode-toolkit/ for support and download. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/clearcode-toolkit/README.rst b/clearcode-toolkit/README.rst new file mode 100644 index 00000000..ce01b711 --- /dev/null +++ b/clearcode-toolkit/README.rst @@ -0,0 +1,189 @@ +=============================== +ClearCode toolkit +=============================== + +ClearCode is a simple tool to fetch and sync ClearlyDefined data for a local copy. + +ClearlyDefined data are organized as deeply nested trees of JSON files. + +The data that are synchronized include with this tool include: + + - the "definitions" that contain the aggregated data from running multiple scan + tools and if available a manual expert curation + - the "harvests" that contain the actual detailed output of scans (e.g. scancode runs) + +The items are not fetched for now: + + - the "attachments" that are whole original files such as a README file + - the "deadletters" that are scan failure traces when things fail: these are + not available through the API + + +Here are some stats on the ClearlyDefined data files set as of 2020-02-26, +excluding "deadletters" and most attachments: + ++----------------+-------------+-------------+--------------+-----------------+---------+ +| | JSON Files | Directories | Files & Dirs | Gzipped Size | On disk | ++================+=============+=============+==============+=================+=========+ +| ScanCode scans | 9,087,479 | 29,052,667 | 38,140,146 | 139,754,303,291 | ~400 GB | ++----------------+-------------+-------------+--------------+-----------------+---------+ +| Defs. & misc. | 38,796,760 | 44,825,854 | 83,622,614 | 304,861,913,800 | ~1 TB | ++----------------+-------------+-------------+--------------+-----------------+---------+ +| Total | 47,884,239 | 73,878,521 | 121,762,760 | 444,616,217,091 | ~2 TB | ++----------------+-------------+-------------+--------------+-----------------+---------+ + +Such a large number of files breaks about any filesystem: a mere directory +listing can take days to complete. To avoid these file size and number issues, +the JSON data fetched from the ClearlyDefined API are stored as gzipped-compressed +JSON as blobs in a PosgresSQL database keyed by the file path. +That path is the same as the path used in the ClearlyDefined "blob" storage on Azure. +You can also save these as real files gzipped-compressed JSON files (with the caveat +that this will make the filesystem crumble and this may require a special mkfs +invocation to create a filesystems with enough inodes.) + + +Requirements +------------ + +To run this tool, you need: + +- a POSIX OS (Linux) +- Python 3.6+ +- PosgresSQL 9.5+ if you want to handle a large dataset +- plenty of space, bandwidth and CPU. + + +Quick start using a simple file storage +--------------------------------------- + +Run these commands to get started:: + + $ source configure + $ clearsync --help + +For instance, try this command:: + + $ clearsync --output-dir clearly-local --verbose -n3 + +This will fetch continuously everything (definitions, harvests, etc). from +ClearlyDefined using three processes in parallel and save the output as JSON +files in the clearly-local directory. + +You can abort this at anytime with Ctrl+C. + + +WARNING: this may ceate too many files and directory for your file system sanity. +Consider using the PostgreSQL storage instead. + + +Quick start using a database storage +------------------------------------ + +First create a PostgreSQL database. +This requires sudo access. This is tested on Debian and Ubuntu. +:: + + $ ./createdb.sh + + +Then run these commands to get started:: + + $ source configure + $ clearsync --help + + +For instance, try this command:: + + $ clearsync --save-to-db --verbose -n3 + +This will fetch all the latest data items and save them in the "clearcode" +PostgresDB using three processes in parallel for fetching. +You can abort this at anytime with Ctrl+C. + + +Basic tests can be run with the following command:: + + $ ./manage.py test clearcode --verbosity=2 + + + +Using the Rest API and webserver to import and export items from ClearCode +-------------------------------------------------------------------------- + +This assumes you have already populated your database even partially. +In a first shell, start the webserver:: + + $ source configure + $ ./manage.py runserver + +You can then visit the API at http://127.0.0.1:8000/api/ + +In a second shell, you can run the command line API client tool to export data +fetched from ClearlyDefined:: + + $ source configure + $ python etc/scripts/clearcode-api-backup.py \ + --api-root-url http://127.0.0.1:8000/api/ \ + --last-modified-date 2020-06-20 + + Starting backup from http://127.0.0.1:8000/api/ + Collecting cditems... + 821 total + [...........................................................................] + 821 cditems collected. + Backup location: /etc/scripts/clearcode_backup_2020-06-23_00-30-22 + Backup completed. + +The exported backup is saved as a single JSON file in a directory created for +this run named with a timestamp such as clearcode_backup_2020-06-22_21-04-48. + + +In that second shell, you can then run the command line API client tool to +import data saved from the export/backup run above:: + + $ python etc/scripts/clearcode-api-import.py \ + --clearcode-target-api-url http://127.0.0.1:8000/api/ \ + --backup-directory etc/scripts/clearcode_backup_2020-06-23_00-30-22/ + + Importing objects from ../etc/scripts/clearcode_backup_2020-06-23_00-30-22 to http://127.0.0.1:8000/api/ + Copying 821 cditems...........................................Copy completed. + Results saved in /etc/scripts/copy_results_2020-06-23_00-32-37.json + +This would likely something you would run on an isolated ClearCode DB that +you want to keep current with items exported from a live replicating DB. + +Note that these tools have minimal external requirements: only the requests +library and have been designed to be used as single files that can be copied +around. + +See also for help on these two utilities:: + + $ python etc/scripts/clearcode-api-backup.py -h + $ python etc/scripts/clearcode-api-import.py -h + + +Support +------- + +Enter a ticket with bugs, issues or questions at +https://github.com/nexB/clearcode-toolkit/ + +And join us to chat on Gitter (also by IRC) at +https://gitter.im/aboutcode-org/discuss + + +Release TODO +------------ + +- Merge in master and tag release. +- pip install wheel twine +- rm dist/* +- python setup.py release +- twine upload dist/* + + +License +------- + +Apache-2.0 + diff --git a/clearcode-toolkit/apache-2.0.LICENSE b/clearcode-toolkit/apache-2.0.LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/clearcode-toolkit/apache-2.0.LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/clearcode-toolkit/configure b/clearcode-toolkit/configure new file mode 100755 index 00000000..a22125d9 --- /dev/null +++ b/clearcode-toolkit/configure @@ -0,0 +1,7 @@ +#!/bin/bash +# +# Copyright (c) nexB Inc. and others. All rights reserved. + +python3 -m venv . +source bin/activate +pip install -e . diff --git a/clearcode-toolkit/createdb.sh b/clearcode-toolkit/createdb.sh new file mode 100755 index 00000000..94eb15ae --- /dev/null +++ b/clearcode-toolkit/createdb.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Copyright (c) nexB Inc. and others. All rights reserved. + +echo "-> Create the ClearCode database" + +# CREATEDB is required for clearcode in order to run tests in the future +sudo -u postgres psql <}10-GyLqGg5sD+E4yMu?l>rtPLzDqCVk9jypnwscwa|m$2 zE!+nBJIyeR0bXL4*qPw(cU9z63}eDfjV;JhTaRzp9;B!ntZERfzAN~|i4#hCA<70J zTXvn+p^Maj5jT~OLch5cCM)~6s$BBePk+m*G;R!PrBMt?#VvIXLc&j5319gw!d&Q ze&u8KI>72p(B8MF#Lm*B&)1M5YviL|C`G?iy;P?j`^qx%x?yR!c_nq<<4ecG@8~4d zlM~+KL9KF}-dH~#Lp-2)s!5c zuku*va$o57J$LS0Vq#)=bY|T3{M+fpH`2;7Gc&XDD)Syxm6Vj6DepL6)p@1(Nl3*P z>Z7jA{HM9augfZ)hPB-ceRKERySxjZ(=K;pU+v0E`jUVBOF>G{!<=tb7puF@G)-P; znT%u;HF`m^8m=Qi{wfBu!)FKojpH~gmWpqvFb^R&&I#ba%^Qiy#{G~JVS0d+=qUY0+=5tej-+4TLKV!Zq zcfP!E{&8u|)4L0`2@9`oEYzpWKPg{$moeXzyU?1o@V;Q7wPc~KV&QAZ!ncbH{Z|(T zQx|^ZEDS$b7%hMH>=}*r;`QsAy1KfW*X=Lge6DV4t9#ec)YR1czU|YePftG&*L044 z_|*Gk=*{z?hF2r+>jyu-9{texv%R)!^!dQ=y6(|8Kjzx|dK!DjTKdNuhG*LcCqIq% zw2aPv8k_Czcr#y9JzrNd|K{a>OfBrbv_HChyw$Srx;alT;SNlT$ z`-Q=_g`c#A@w$b{=7nGFU%!4G931>IJk>ij*f;&7e{$se^zhfwUth;&1}A2QW_}J$ z&&~IC&kuf`8ys91>sy%ku`u1e@Oxl)6jGP`BT1yd)znIDA>JHg{yp^D}il zDLR?eZJ8hPRwg*R3bQ&Mk(GP(yP0zJq0yQv<4*~P>Jws%%kPC;Kk9GGm>8a*ZV>eS z?ZU!XW8Glewd-Xk9#^!_?jQa&vk=_5uwb{6@6YY)h78G=DMrKy7zf4@!l?cK_-hEu zJ~fq#5f3BQ<;<9>`E{6Nx?g@6`Wj>Qov=!6<4&Od@SWh^C)m7?hS}Ub{&RKx?|ah4 znj--&?KIA6>mC{{|7^9~-V2XcSA5G}wQ<}kvMMg^<#2EaT8xhwhkiHQEzY<8M3REr%l`(j4EDcaf+S0 zSj3th5|C;jPVH>#BjrQzw zKb5U3G?uM;N1UN6M}MbGGMnZ`P=E zh6h<$#l7*FuiC+~vwGxn30BfymsbepjoLJavA!!-aVLa5+asIH(=qVmyMqv`qma!Z zrl8{cR%~)a@)38_d#QpG+x9pzm%pmLPSs-?PU{n|-X}&dHg)@HF?x`8D5rsD-C4DQ z*JGa%OC6%a`FN{XE+RE3)J? z*(bJ#;mba`7e=`k?3t^rWQdQBNBzQ{B^}<7Ra&0v z$=PE$mAENO^%j4KO>TxujQYI~=I$FZ{6j3=MIJWkbl7}}_-453m1Sz1k+lamf6g)g z(!tzk9&MaWe$x5Dvc{e1t}WL5&j#97t6PkIKo%2g(X>;Q$mD}oIUlL-)ew@=Tb$N0@J>vl9@{rA3H6g2+ z-pMc+4{j|bZ*1RKz%zTCIizIzmoE@81NO$}CJEGC%AQ%Ry4W#g{B@-44mVCxW zi>`{hJ_WL^8W zzRzA@L0I&F%&jjoop&uvu3oq-5^~f}lu;o@GsdJ!aoMQN$zk%9S3kDb+&U-AXnBXJ z`ZinjxaP?1P#40Rok5PAcEQJob`1)1GWq(>73Jj2%IMwJXl(ebIODK^(e$mMFys1V zN}}n_JF&yJN7fF0RGq5pC|Ff7`bWfiW7%-cb7Ek;@w-=FEo&yJ-W$2~tfk~H!kZPsH}@Hr6@vRXA{;=)zQ5ao(nT3+4(Z;$SN8(C2B zxx)YU3hJNFyl*)d&a8U+Y*)^BT1ZIfm2Y8L_pt|h)w3cyci-4@VvObMLUu<%LEQAF zg;d$D?8!TF61&GWN`ADLt-59VBEGI{j{TLZtm1{9Ak)qSoLbf5caD^Od$szi>Wa0NWGVOQR2_L z=Q*i&EzCQKu_NH{%;AQ1&qft(VNASCHKfz@ae8sFm!6y3sUGuPyNB9aeybd{o1(h( zowMF9jd=-ON`CT^vAR+p1hYO0ey? z$M%+^YdaO2kNZ$n>Hl%p_2rMp$1<+e-3}MJc(Uo?WoMfJ6_2%RSGZylLEJA^Ft}Zn zSk@XmKIWFNVPEmG{TvTB3E6g*GO2PrT-RQmNek~nD{yr2?dyNn-@nr_k8sdd>3zrtjoU-R{R1Z-=HygWRS6yr<{EUc z)J}0d-&W(Z5WTj0+*NyL^a_P7d_#dl`m@*LJcWmXPsKhy^}RNq?`wW)&&3ZheyP>{ zh8h;a+(ZZL(gCIv&zkF7OE&H=pAfcxP}P%vbLg{>mfOf{9?|QpJHOg-j^4JT4(&c( zUYJuMc&a?wk37!Edxki6;ZEU0rtbL#(S^(2ZkR+<&XcrLx}yuTKAbhPza3X)_T2s> zVEWqnOp2iRmQM_x2V&+H!dx;RHAR=K3)-zw^H{MXW?S^n`KD7m&L-SlUl~LLnAbhp z_WpGLU}s?;Q>}1o&vc1n`_Go@=@7@aKR$RL(WZ3rMmklI5~ah=4l$JftT~eJ5f=!Wdt)5dsfVUT$f%>@#BIm_NQ%c;=ki z6mN+L_f*B}K8H6tnQjUl1|PX%e{f%B%P;?t(%9HI*4Ri+PnYVJ?>OD>sJPGK0kQ0E zCG+F$;kqJK)7@m3>#>crtF~T`^;xKMKaU&oD4hAmHvHJwZ`l8(P-ox^(g{}mnFRjz zh3AIVoGTWdaIsuU<{_$t6a|JHvOTM^wo>}4mvz=bc79h$)4Ks@o0P=H9|Vg<8wH3- zGKA#rV%U^QzRDANZTXtLl^eK8THY^Kv5M#W+>85t`Me4yF3zoQU;Rdct?`lqDF=c= z$)f8q;ko2;6K_nu<{G;rwvvCe@KUvjX>ef{yZ(gzi|6YYQsTzVC0#f9u!f1=-0&rZ z7%j0yMnJ$;vhJEegn~Ii>Nq)8T%7gEE~RZkn+uh<#RROD4A~*W_mi{p=AYp6#mjF< z-j(_D?W>vcUDdNU)f$&=sJ+F9En7j|*|%%0zKZlRt+S7A`=uDD?0z6$6ft&&v)-Kf z(s54ZTQwRh31|Ix?n~VJYD7jYL5aUrX^oAxzzwIhM!f7yuF~vJt1m?;F%8t|zcNm^ zvZCPD_PYC5zU(@$#H6m4BrmbwX{~U@J4~oU`xuv?&N<0ZgB6ltayJ;y^s&nr6dd*~ zKm0WE4O@k_QrfkrGM>j)5PYWCj}^pUJ9asEr6BqDuWM|-yyOd?^h#1(ho6yaF{#`O ziNEfw=oMc}B@f4?)kPh#sEe{0y(h3SIcgLRxitVl89`BMAiwE(zuFnKgWuB(RUIr!nXDBf&fF zGkBp+fC1TLXWsUMVf6L_J3Bkxvp`pt{@^`}L%#O%$K8)!G}rMsqrhAFi|@q{AD86C zFqW@olZ5Q6Rs(Y0uOrG?1V2iDouOrjHtO=}RVo)278;dTG+zl|O)n#?rH1jI5KY`r zN>rYh`u)SRLH@B(@Av0xFKvJDkk$T=0M}&T-FHsJLs^z#`ghBu!&;If?RRbx)#bRB z8tJA|X}ezjkf1RteMz#iC| z;p~GvQoM%(DUvt1Ghdq`PWr6OA~NsX zF&BRSZRe?gUo%HDz2;UwXp}W1vv{gIU%6|(eVB9mOz!t6z0YI2`^qQpiDj4%r0Ru> zoO|K7_fAXA&MU9I^D2*)e}5hu#7M9dKF50{^VII0FRbe-{oFSCC0wjje03nnB-!t4 z+3?A2`LEx~ZhzBW7+5u&Q7GG_pQ2=%Z2t7z7TW`b!!2u08akg#acUVHJj*+h7RR#p zAnB(~ap0H33)U^`(|(v9h;;wB#@DRm7 zo46mVlT%=9GG(42@NB0gCvcF59ZDgtj*`iXqlYJNqsOIJK<=hs~6NpYpP zc(+VT_tH#q|XE);A(!6~rGWq0^N_I6j-wT=a^i?Vsd1VUfFna+M3nqQ={;bzOr?>iY~R+;h} zO+B+@(D#)%D9!aJxMSOvi`-8{j2QD8YxpIziHf9}yN>HZ@+tnWgE!qIe$vyx;+w;TF8CTeFBU3a);jSIfoJ9PKpis7frV)MFeh8m{`iR+2O z2UfDH-urB`-I;cLk}KqV@xU^Jg=?*$r{-#gL#KvFB%h}^Ib>n#Re>R3(9m}T4TdRz3m3*6TI!E&#w}}jkJn_-CfaO9QF~<8^;g+j*?PnAtvU=(@4vV+e zUD(tsy5^iV_ZPm~g&%%Nj40eWdbCU|mH9n(X#V5rF} z;uzx!2BsAZ3(qkTm?sMZ{%48#85o(ESyNWgp1*D{9h@|x@s+%^e zZBgH`ldP+!Z(wL{VQFQ(XRnQ;)1kx8F0My>eEs|bj-LoT6Mi=0-1$gqY+U@6gsX|y zl2TIB(lau%vhO}9EGm9jQd(C2^x1P-P3?=9uNt8f+47c z6v|H6e)&$plVuXhW}|!#-VGe9B~`vi{X|Q{SN8vIVPXHjR`z#c|JK!paWOG~^O#m( zTG*62$>knqNK6S?MKEhHX6=sWASN?LH&e8i8JMJHh!NA_N;<*Rg+W;-g^DTaljR7{ zrHSduRLqz{CDJf>O(S6O6f!17C^8jePf3VPWRa4GD^i52{$%E~^i)E%p|N>^f%Y=^ zEZnIVh>=KMWK5bwYL3U^%^J|7Xku{5tec8Yg#)GoKcUO?$?8$2OkTRAW^;zplmvL~ zGGvm5hAvGBSxJSNqA8#UGg7EF8t<6&Q}3zA!cyV~6t%gNskX2TvOI~1rKP7NE^CYO zF(QkuL^q0&6k!|%4nrG7G%bF3csv@!h>S){gmG;QF(Qp%Dhi*3C)p4gi0LC_g;j)z zgqWwH^p%)3;6{LJrRgbAri=m@GDXKk(Z7-6v49jRHzvjZZxnnu_GIQ%0!)sI3;|Cp z!rE+z$;@bpxXIBInWYG3-SBL*2=E15jc-`AAu<$+vXna2+FsOu!uI#!6Ot~4=$WV`bN+?Zc zZ7`yOb&T5L2&N2rHtG~@P6Mhx5#Bcuzw{19mBXZjNX_A<3~)zLMu;GlbRr#HO(kIM zX5DCJ0wfo4@J10&Q4yC(bNqnG1iPWF*a&!%sgudoNWvr#N$kRSqD{- zOov}M?JqbWAQBM>sF*w0p9AKGzz*IR(H?=PTqVR~6f!^Eaf{$rSi~k8SHQ-Q86xlB z^8wY!PKL%_2tFf*E{m6lG!%%zC~O=MwA0LRfaC8vssyAbGt&T^tR%&Hbz~r2uq{lI z#!tgkr~yY%#0h;5LbkJOHUsZRHz&g{CKLV;A(+^@hQ@?TRAzz$FbT61&IANADv?>5 z#3Jv4KAH`X0gy&fIuap{*zU=OfIqGhu*6iDkU|&oG)bkTHJbeJ-G+S``4h2E>dJS%}C$a)CKXlhDf1D3lH= z5lf+xuqsz%r~mwH>QA9ow-bTmh$xonOn{7lfC@8svxAJ84W^>OS|I*Y5s`V36;>|#fsAdU+Tdv=Em_BaYGZ?W z0Mt-8fpc*vq3nr2CNX^or7gO5!B8pc)Ma3i3#EI|nX1O^4XI^3j^LfR>4X_z|zEAD@684V$i0~SDwhvCXIxDRg{ z;2wYk!;LQ&MKc{+K#YGsfu1Xd*s2g!9b^guOo=q*8!C z2RgIE;t-~|7PC2|Oqd){2vNvI1@lj#+Ux+gxq!)l-(V1-uxfWm z&WXv6E}3Ranu3Vm#0U?dV~nVRG9Ui=_-YM0o1teSM)9JrRB@SsQ3n;g8-WX-bzz$>fka5K@ZL4zXpN@iYe;50tq?b5)8(H$$No-j3W%d z9L+AnBH_upB%Iq4>($bupv|B%W(k*n#m)6_NoB z^cc}Qf)SGeoG8q3)d80wyCGT$YJi*KLH6>ZYzMb9acIzAV1xjaCCL!b$hAn}X|up9 zoLN%-{!2p1PZ1@7ju3`MDse=c6&V(0;P5vQ@=UQw^NLw zUAd7Gg5>4^3sp2~Lrj2HWMDF6fY=b_Te?9JkkgX=;ci0^7||0&SQD63JP|Uf7os*J zph!T(c4TuJWP!!i;~rQEDGetwMQ+IEXrjQW2)Zyn(8PbuMGC>EP8Z#aK@GYVWn%<; zTr(k+fV)U9a1k|e_Yl&SBtg3u8PCKtq#4pszI8{ip+AxyJoI(?Co>UvVCf*BiokI= zv{ACdYb1zl2fz-3FP^WYQBA_2%YvjOEK!cR0JL=}Fh6hs4aqW>G!@7fAWzeA(7^|n z36eJ&f?m>k3WK;1aJmM)GNMoC0Q1J^X7q^1U&sXRWgTIVzq()!ur^4r2mlw(YXAq4 ztHDE{2H*+F4uB0Duu#^}l^Ns$I;G)dncW1i(O-hVoj}7)j$&@Pp-#(HLJ<}LPpVBv zM2i9sqC^qI9grU|g`fq6>~I8@PUn7lRe&2Ez>1sS7(^jFk;*D1rAE~-6{AxJ9t3o! zEV5ce2nRQO0_F?2NJs#wgX#oaqu_N_cv_|z1T77{tBJ}{c8E(Py|~rm=+Tb273rzP zNfMJA=yHGYKKAqxT~Y|B&>)axv(Cc%=xjno6k3VkmM*0If$ax#I_j4D^Kq?&v}ougM62 zB^22tZuEW%4xMO(bR31oW)OfN35y{8cT@d9yDFO0s`w|f{=#DzDiy#HIOR&UyBx5U zf*`;+0v3dF4GpRidb~g|dufM{kV%9y_n07h=o17YQLIP!M1nFLEUB0TJPGuLp)LRx zmJmfjvV4(`4Q(_UT3}?m31k{wSS^hnK!7G_FhHzTp?E^28v-1FjfTe;Tn4oO5CBF7 zwMGSOle|#*rf8(EZYm0PB5C&X1Ho(1KL`m5J_y{Btb^D#g-(KPAsi+}rIY`P04M;W ztXNZUtSsKUq%7O$2nBE8de|X^r@0MT4m1h8A*GQngFx+3#zMW6_rPydk{>Js51k)9Qh`c_2f{+pBNq)Wpk#23qSi^LfO3dB0J&FSB2laLk9Pfaz-+Z&57g^_T z-aw@!b86LM(`t$P>9P?ON%$-wIR0Zps2-3*0W&ZSMTFA79z!!VL^&RpfKVVXFVPQ9 zIuO+=*v)<*i|t^2xD3S`)X7kNfoH|=kzhUaM1iNsC`hx?L>0(5+$l)HgDl?y#fdv= z^*P|EM8zYOh-^nzgh|N+LbD0NMg^d^l-snI=@}v`{m;l~JwOjEgh%n`?)_uJ_{@8t z6wND;WYY!pK`>c?n=42YHgv0GwF49arGW=XctD@3gX)Dnami2%PqBLUV3@LV$V zVyeKKI*5Ydho>2Q138bP4=B}6W+p&3#`$?=0?09pf@BH3xPuTtl^(IS7ts=-$T0~G zoy>T><0ksuAz_IwP$IAa4C}&G*6xIa=w`%M_(D;83VNGpj>Z6;Zd_$}(Mb?&wjH!8 zOc($z%FE-T0*ir6PLGzKm-N2j~^hNcPIQn8WSWPq8P|b zo2c~46y0iTW~xAMqJjtq^@y@4D&U}-iMLshFP6jwD)bgt>w@qc8?qW-y9Rs+bz2Pa zjEPSMUkD3MNiGlNMSwZ+2CMEC1T?dEz}GM+Bx^b(;5PaQb_>{99JBxNAMQvR4se7& zWGy-${il7N$VyC4xkPVXf>s1bz-;jdh83Y$M;}4Kfx?82VnAU)@(r$AVJS(&9sE%- z>%-aBvfXHYn9x%^ArU*FC$>K`W-2Rp_Pp_UWlryxvULKoXFmi?NB0_Tnl^!YBCjxh z5n{1uaK)w02yQ0`F(9F8xM8WPM#Z@y0!MmkG3sW({kUBr2w_&JD}~oe(xlepXd(lh zI)DL|z>NXvp~ryVp?`*h+riWY-U1jJL({4%3$ZBPzaPR!gykWCz%!mpNx>cgwB-=! z=qla;wN8+o@r1YpCiGU=B1X@GHv_1fEj-KvnI$s;BK8{f8y(+uCv4MO_?CW2|LOH%9qUhR*-oZu0w@7}-CSiF07O!;|;R_yAMB8eh0K1nG zngh~LxI)4E!>kSXF3ext1)wYm6ibIXswJR&-GM6;@D1EeW*wiakG6I2zY5*#4wqK0g2JR~+~wL(J}WkbXWD6(j%HzQ00|p^pKff823=6T6#7mdvI7O}`Pnt29_-a( zNM2@B^hAS_&A%T~5G)H~%^vTEa6_20(DzDORZS1=W=f*7hu|olCWo4Q@=0Yn^l`6RT_e z+?de0ud!t)U0k7+T~CJ3zk74i%7h>0549?0ENpydC$1lNzn5w(Dp#0%Pj>HNC68zm z(}NdNlb(7sJ|IXOPbnw&rrhynU-y%|H&(k*@%T%}4~*1X4prAaH<0-zWD?o#K{*fx zi=_uPUYwy+MyFspKv0H*%K%2Cr*@|Ucis$a&`GC(2W-vojS+fPA%%#R&hlV!RLDrl zNv%PI=Li>2QJV(NL;ZhzK!6>pCc(Dsi;sYJ(B2BD^+nQHEcRhE96t0dBRo66)}I5K z5+oX2AK;?(;D53nMLPs}4ed6fw^1N8LNcZ!&}_-^^zZ@6gmePE*M>T?=z~CyVF;hl zok13%A)rJ+r(qSK+y-;SJ2!aTh#~Dj#XOHFn$QSb(2JBOxuUi??DIlX2R_CR#tQ|( zNrO=xlWZ1iW{5B7z8V-oY4LBpXoodE9+w!XB7kxSI2S7HBCMkb`a4jGE%uu9kPxG* zIuxR*#qwLgE_jNf_YgobXd`pbs|N6}4r=+KT>Tf#Xo>%n``<~BVM8nO-)~F4fJhPL zz&%IJ3CL>OBR!iFe%|kEqq|(qm5)v>@)=@Ek!kj0Q!>TQFMBjM{Y;B~by_Xcw7Abs z@~7NzYF2u2VY%$XhM4){V-C`Tn@vPcbe_u;@S2L%WE|hS*L|=17fVWwIp4VrvH{K; zl$d8`SH#`ErxUb*t*qiac(8G(HnH8Qg}uCNbHQiHTMoIBT)u3hdBpACizd`(s+xCy zxko9Iv)V5`G2@{7dgCpIiM+`@J2rT+X>DmY=y$6-spa7JcEz>f-r7qY_DnhJKBVm; zoiIHUa^vfGk|MQn|L24Mgc1eZvKv`p=L`x*WGS3K9N%95u;;Mn+3KypnGx@ z!5dP7;!+j;&vj6U{VU*6jV*;64rIsT6$YH>6I0XiJr5v36n4-|Q(24^W*8czaw;l0 z7JHWBi~NX_Kdz%eV*w*VIR4`x-Zoo2lmBngax|QI%ppj7v z;w4<7AqntTS7#vlu`9M3H-FI79FT{;%wTVyN`8}zGYLRRwoq(9(OhBZvFWF&t4BN zmZ+i1xo?yge`F&P*qS#=CrzD*avs(_z&y9hZcN~`?EUzu~nNZz!)0yj+Yqt7XOA%s^R{CMvJ><2n z`&02ZOtvGvVx=Tf6w! zlHRT&47%v9C|UjWsKF`0GWW))11u%z?2OaoY9fA9G(sDadRY@i+=!YBbcLm`6E&|-qr00@Si9BygXzy|6; zLOi|?ZIFa%EaeCMa0*kg6g~9mJwpNPF_fS+4{{$CUjJo6^JG)KCRp1+rf1NPvXI1xu7enJgPyf%Zi z#shlse*5ACAdSNE7fpbO2v3Me*r!2JvA8ZW!3wL49=3{rFYo_UCsk zo_<+gWs@vtPfTt^fgK@(?luqvI zSzY2>RPc0NSKY1C40CRE9wSbrEy+Qd-q&jrUwM1&OS)2NX17wPNAyr7t!qx<^iAs$ zBYz49vr`<$9+Kp$c59j9$CYn>87f{(@qb5KSAOzvv#e}StRB;!BYAh69EutFo87la zJ+3LTqxoJGR_nTa*-~3Z?d=!Kq3c67q)RVLNm_$E?c+^+-^z}(sUN&YxK{W1PW;*) z2T>7X-9+w0?4JDh7anA)Z7^xr&yd6@cv6x1>w2b3+}<`fXtsK6EalFR-&yb97^G|b z<8qbz3HCA`>@as|Oj7>mSNB#eV5@aacw|{$eH;pRmG;&>wq?%E?Ax&9x#guBcUSDS zpqjlC6BpEMeAI_s(K3AV^YGP`)g_$EN0#r;q=xnEUERpHEB0%MTGuHzKl$#;ho6*s z{U_H}tETp4N-2_bcRjgr_*&Nf@0VwM_K`wnefATqTROB$&+fU^z2k<+(f6(svz!%I zA_TLIQ*TC<)mt{_=56>av0~k3EBj|w#WDghZpul#H&va6FNl2FeD4KOFOPWlJM%ua zk#yl|3gyMQw-N*DusPnpE_8{#;m`^6&LLYxPq5=zZBbGFOU9xofi9)Lzi3~=Kb;E} zB{7Jm?I^&Ym)#r@!sP|c>Il@7Ma3`#1y2zkKxp@T34nS>AbZk}4Ma}>JR4D90T0ks zE-K0b@G=_dF|-T!pF$G47kEhP1=1TRfNs!K|2c&Xnv=XU>PcinSIroWrjwL_jDdi` ztwG<|>Rra`@=Kbs?zwDR?~=cE{7c!{(CS^+ODA9Cu{{sqcJBTzzvdgU>+*o&i`>ut z^%B?QhaUH|ef8X~5g79<{g9!_tGV4f)@+yeE-cjB-+fv{I`x#3?@hyjwzf^LTep|> zof)C|8vNX?&5?Yea;Q(?_IUmA$6M0AmGi7IP;M-KOuZOzNbcdy!|$%KcB)&g#eP-~ zS3lhb6r)Y0G887DWKJ*=MCf7)MXVvDMrtz@%hx$u2r#?Ib% ztg}54zni{aQ*}NF5CSrXIu|dOaTNShW&aw=xZDE6LEZ3QsZU0leqvF%g#fG4P|pfB zGO46iP$f}x)S%6;f297eN0vg}p-oGx*VBByso7I!<`=v z5qg{}nB<}jem8IQQ%Ul9dRxUYC2Otf_TJaz)x`HErz6L#N55J*t@*VnV1%!{@`8j@ z#W9AdJXM}UcdBO0baPif-|=WSiFa=D@Dsi>{_c<7jd+S&7wu7-w@8Y3IPm(a{NrKA z+cno%rf!nVH%%V(cHFLP^K_NnXu9fX(vBYwA2zh_CYhZM_un?1^({#-F{+~S$E3Ia z-Fd#V*#kdw&)SFHWczHTsr7p@Kb@RacOkm{cpn_qaIg;voGWYb*P=7ku^l&{6hsrw>JbbP=AE^iRXH^ z#7O47X>6(4Zz=4yE2|+UF`xOiqvc4(O}Tgag(V5cYPz$X?3WTL7tAlX`NVGyxe?C)$0b=ujh{JdLL!~7=~c9w z{_i}hjcoGsrOFx?H9C;U=~|sksDUVt$Lno+>ygeuIGx}%jTD^GTqH(fGJXv5InF1r zxpp4suMtobh||n4)P!XefHbavjt@azUMiQ#*ybuY4mhHX_uAn(u1LDIa*$T<^e@7_c zKB4}_ng?C}$KiHFM56CM&7-BV1sZBJe4`qPH_#*O zhU$%te%V|z6 z2W%V%?KD=er|o=T-|EMq-d)iCK%~mHo4Mv|M-3$G0KMv}e|oqlVd$ zZ)Mwm31yc!y?nqMJkIKQP$Sqcf73Z7&+9%fP9EseIJL^eXpofV@=~x-_f4m%GmDkaTm01O&EG5LNRjG*uPp4+$M<(zn#9FosRHFmwW%CY@t$v z%jH(htr`QjJ3~nk znU3kSZ+7jqj4>lC;-;fw%0zF_w&6{7I#?r<4mt5U!9elYxDOr7Xk&8TnhC0tDZZ>41cTa zvwg6;Sbys43u>za+iAnWjh-X!2hCV(B^ZK=EgqJ{wlvqN98+l&YLy5%wYy4P>59zj z*1K(#-b1IDc0@GtJ!)30xGO!ZCD1QCw}&*wk@F`j^2P%8J4gOwT2imgm(a-FKe>p% zt#>=Kq%PlNrLp@LnSA78MS)ouwO3c<)4kjbzso0E-WbT7z57Ay=q;DLU;Lt4e&-Aa zL;(pbt2bC3xf2tiX*E@gARlLT0@yUV9`m?p8Ic;_s3AE&L zmu_b*Sw2lo*@U8W=~o8ib)C^iK&J$9!9G^19i79CHp>TgvO>DhnWaEziKMOJ%LAA4M6*D^27AC->1wlOUg9|d2R zBuP{p-e$!-oEPxqfsG35yBAD(2G$aCx3%30F55@HmMc3|_u&|`wfuVTE7o52j{LQs z)jX&5gpD63K9f_O`k5eP@`pkh%J3a~KG|kyBip`}Lhs$tzewW(-_ZdQ zgJ42OTb4ji#3h;oB0MA6f%)rJu(^tr(zlQ)oegip0mj4B^O`!y+ZX&{WW;MG)qYLGUv{piOaVfUnr# z+bqZiAb*!we$ppSjPbE5z$-e$HEFSJG zGPwLpbxPOr2e;Y<^mS+K9N$li=omk~`m*V3T!X>fda2Pu8`tv7B|dM8v}Q=Zv?hl; z8>=)#w7%GBx##MnPR|Z~a9m*|++zRs>ZL7Oh7+@=dHa7!s;H-5XDnO5Y>8|EpFeOLC?>FOkyH#G-jRL=*`My*I-@afljb0oc{FmnTw#~s(Q^GwPc*G&#mp5%Q0 zE_qpMuJAc)*UsdjTHCHB&;7FKj)=48x&%Z#gL_J|04H%d2h zKjb6y{(0j5tdATTv1Esn8!Kw^Pk0bSloV#G6i$)y&)>axYCrjxrsKSgNV<;Fns9=o z`a^4>s=HqE_vc8ZbDiC!Pdj7>k42uTVefgUNt)dMi+pU|s^0t0VuI_fG`G}?#ty*d zy)okU&tPrX@Wzn`Ac!*Sp?AsP%P=Ta-~IweJhhQffQlZ^otx;LH+)~{22l84?9g8T ztm&JXOZ`&%DF_e`*_}uH1APx0Ww$8y@0PxhrgFWt#5i_mBxl9ltby0Z`D#0++l`+J zDGBfX^Dxm~@5|ohIR}nJcOFT+bnh&~vB$R7s>blYQK^eX#FuQI^yE*^Gpk`xe8U`l z?C}-vfTZ-P!eh6OmWnGK-)SrQhg8y_)ixzt`*q#PzOsqz+^?1u(H{R!a12I zkeF>CRFKNPr}xjUAOb~@SgjE}zWJWl<+DL)9iLc7uAL415P4GLPynH1{q+pdtS4R>vhCX1x=~|Q>s|gBdAe_GlYDT+$lk`OhObJ|cIb`Wn5uQe zpl9imqO(Qye!0VPf69#Pz1373>~th!mu)&~iVY~8=4ifZ)Ktt_;l9gj>ow*Fn?Lv5 z5iSt0bC`ck$uDbljfH6{CL{U=AR4Q}fzYkjNM1Z=gi{*)4xXi*Wpx3D_< za6(I)u6~WAWWsvoIP&X)M>i@MwmLH92pkzU?A~?Mi5A$Fpi;}Q_iZOrdVs&W&!;`L z{09_yG36RJjc0ZuEA-`^qg3+iR(COc;%nNkwA~t8dE8QnOb(2!*lE!{;;&nf8??1? z6T|rFLfgr9cfYL4@9GM6SOACObj#OahYQMYE;BXwy!<13qmoL>@Go5^vAjGvAU8W_ z&AWV`EXDRq9}DEF8Dd_a@@Y(0JfEUhdhYerwImVI%{>$ypKlJtYn0|LdDwlZGJf|` z>gn~1m8Eq%{hdFxJq&bV&pF|F!iOt>SNBd~6k9cAn8o1aFKI#oMNp5IYv-=BY@Ihg z7-0L=jP3YjH-5GfQHl!um1j3k+L&jfnZoI`#bHbRirdPCtw94;v*rU?tKCP*4LS}1 zbF!<<9v*5oYm+=AlP|>LN6isRD$cocElAj#t$0tETCbNhZKb}#ULO5P{+x?s*3KPU z%)ec{_Tuf4-y25vZ%CEw;@k44-fwkMZ~voc@zftb%AB9f>>_8i)}Gp6l_7EemcMBI z(fW+w-qjzs7G1=oUe9aJhQE zx>pq|MglY);?2w^@=uOGr5|pl(9o6_D)ixKHY5&3L*k-3hDHNk8K7?BVq1bPkdb&s z8*qxo^b|zD5w>h`qKET!pa9W!t${Y`w(z5#9B_jaN;D48b|5Xv?9fIk8-1gZn2I)W zp-K*i`=R6bmn7(yk)WjSX;a9k$-gKa(b-G9GNJu#%`Xai{Ba79`mR)?yWzP3DP8dQ zVlG=M9_ZBp4DJBq;`@H+a9HRb)S+asNAz?b_8O_^oCxawJ5Vupm|H0;Y%@^2mJ}-5 zCj_1YS_4DEfke2=Gth3r0ZJdeK)er=5=O+bJoYBATF#D)#bghfTlzan4R?OI=zY3F zQ_GMe#_MPDMMrXfPL!qQzIh$x1+4V+l>yX$C{g`_dXPvZkW`Im`--w z^G2&T>Gb-(X~i2MCd1w38wpW^oRDyu7BHQhO}BX}b-du)(_*3Q9VvUY9hN7r-P&wA zFQDO-|7&R7Gsk)lhM6Fa+a{0PI1`;MtgtJ7wViBPd1b-;Zu!M^AM<{OUNh1AuHVV2 z+8lmaE{>q(Yv_I2@O@6|HIw>1-&E@u+>ga=57_s5bIr4+rqR6oFQ@-RnJ-`;md_;> zMqEANwxMlx_VU<~**XUkZBK1IN1IOWWS6bxK7vt7&viAo9XYQNl%HI1tnyTfrtj%o zEkln+;(00YbdIfkTT12vmT=}f#bE96 zPEW41_-zq!!xllSD3m8M*M;?;@uW7U-C6lQ5std(!M6zDOM4;kO^Sa#87vKa2}cv? zPDY;NLOO)*e{eQJCSU`7`xRjsb*br?`!5pLllr1Nl*aB`@Uf<5fBIfGq9?9@YF)nNLfCbqX)vx%OaI&|_O!u8T@W@$9bL*cBd`uVdEn1OPI6{!nxw^GmG?lz4Hd_lOS!C<3-Y@yBS@$vWaH6ioh+JzBN4lHu?X61o z`o;+k8iCfvzY}gsg&%vfS18nKFRR+`7P)VsnH_DbsfT1M#2h`|*9+Z~&>fE3cd_Z> zP~Y32m5Ho7J$M9v@|jznU%)&>iDxP5`s%l8%GnRjZoBQddMNe2giK6ezUuY2{!)Hc z{MGf_{ja>*t$XcCP}b((s+EjNeG%3gJC+|Q)=baQ;c_R~@9>cA)UtW|oHO^C7uSpG z7xqL}ZLP-gz4m9n_|LWI$#_a<(`5G$ZmUw=AGXwPb(_X|E+-Xu9v=I_^GxzO>rVggA)nbK16%x_ zCmZ)+_rG7Z{;D_jfJ&H@Y?145bf10r*iy5tfVzH)Y@xVs>(zjK-i}(2VXd3i<(Kx$ z);V+7a(OrOPz$o%Hm!8^bQ3z)CM7ESDA@LchG(9^k;4~#o7Oopq@BM+_SNOii3{Ez zxboB1g0hQ_7iHMQEVuP_ANjL6Zstu%y8gPDxQO2?Ixd`+tzWfzeN2>xiIza?ao^aj zA5{t0Ouf&SuASmjO8R)G|Fx#Gg~1n{b#FbY_J+Rv!y;AmaNKq06ZyT5Tbzdf>}xC= z;oUDsdmYJ{cFdWHDQ^0G{ra{&KOCv=*_FrYAIpgBHvI50eV38r#v^m-m+z$Sxh<8~ zwrj9-=&cyZ!}(DB2JZxHtRU2CW2X1&_T_SxdK|kA@0L5Zi z++rX&FWeydc+a<4BO~Q)wjQ&ov!!{D+@;le7$O}`KYmf$SlY?)iPLCkHf_wQYtx^o z52sgu|KgsvC!+t9>=wz7H=LRZm7;IHt!pag%NWfVkC6D}N7&Q&W1xUcuzql21AfvI zRTijBk%1jIMPsI7X?zDv?+AQf7g7-8V($QUn(z(X0LW6n{7dY=l%$rb4fMTD_&#hn zd~1OfecuBeBZd@5FGmc}ndqgiIg}i*a~%(=7w!Mi&&Z3@D;RVf5pp5^<%a*X@fT01 z;(*ObR+Sz`X_z z3P9%Dqk(@t8J1*@(Ztdry*idgZ#vwcDt)J9y9Cd(ZU{<%Y^D3uyudUc!e7yY0 zras?GVQjWuc1@zo1J+5Xk=r{uuJlB`Tpm@_7r;U7H<-vjRLmSFyF7_f5&i7`pKQv! zz@}Q)^Kt7Q4|EGph|IWZFJRVdW}7q9uH1EAbGdH;Ta~PJxUfw7$%pm)JU71OObb&^ z=SZhiFlQ+9J?*?9(DI#IerF=P^>O5h1PzKFg(jzj6460C^?=`&L6h>oN1>oZ zUd&XmHw^%Qf>o-Te`65*9!0 zu4QgFxEd54!W1(;k$e245l>l0MXh>Tl;Op)87=j`d;4{_eJ@rIxLRqKMDz6E8eYI; zPp>-pJZQOL_u65VeEx!mFCID;Cy}fFxZSKtcqSkQ5M*M!I2;4(Se&?ruR^Kt{S$x;rI?M(OVE?(Q7FGw{Cm z-rx6!g2T-59Olfk*IIk+^?34d(KqHer6&^0cB%J;Zk&MKP`YvKEP463-?a3-$ap3K znr}=ma~i;(nGv~5aC&UWXPAiSBe30G$*(Z4wXnyEBzq%53{K6WQ~p*p7l4+Nqx}4I zCFc?{y>FiG9D1x8ChR+0_*9osr1eNw{AU;*PmdmHwi~V-fJD$dlsa8k7b{7aE0)O3NK@5 zw_qE%^b^R@Kas18BhfF~5EfZeb&L||`xwvDx5?6(5%vgsqh%hHZUVRo%% zXa0J9zfi8$xg2zWj*RqUOoHHyeP$=tZ@{1x-Ah$3qBL$^NZ0PE!*$);GpY6P6|zNByRWiHYFQSK&rA{nwwB?x+)E6MKe6XvEi5vfA> ziMNGfwrYxyU*w$g8AXi4xZA8K(&dB%tQ{_c9x8*=xe;t7x1f`F?QeczWn!hsz-NG? zq(;3=0WVH7D20&SaG%Zjw9nzYxf}Gok+a!UElB%pPkXvoSL<=80@Ge^4Oyka_in}Y z^(PufQFiV(?qdby*@}ctP`fTxoS>&)LRC)o+mkRj9lIIdGLFt-s*WDY>Qu#MFtWQ! zmD?hwdV7hP%<2^Nrcz+jK{4y~hI;q8%3kH2YZF-)Q#~azJ^0)>)+_%hcUwc_i6M9M zc+#~m*R@!S^c14k?8*#EgOt-2B{%0HZ}?df+Vy!+UZbjuzA6tN#UoidOVOvytv^>y z?Knm+ZRX-pdXwcw5_wh*3x5r-ZOO>9k?q+a<5ms!MM z^Q+WJ5|q=^UK>wII4;lyy|;y-%|4}=K3QG5L-ryjLx4PmQ%r4o41@#G+p}>&JX8Yt`QDoJ4E*k z2z5H_fg7Ej@{v_$zks8dqZ+T1N%!ju*Q~EyzuRz{JJ0E8%}O**3gJtFA5f+A|54H4 zO4_}{^*t?oJ`GL60uTl(;Ny6R=H|dW%g}>L_YNRCKrsshX8Rs$7|@A)df!+9yfF79 z>b~hs14cJ=u54_31X9ogsfRG=2`1F&oqKu^3Kg15s{vHOHlz^ch zV9f$LHvhC@0I~&=zBdTNJKmV1Kq5vP`9qfmSm6HAD&f`iq4>ft3$79(-49VejMM@t zEB{E7_k;{M19(*)RtElamjBRML{0H%{-{#iZmfAsH43J6*c0D%g`^w6*J z7Xv-M=Z_EkA+5$lLxXzWHrBIz-Z#sImAvuOn_VxlYxEcw;O&iKf8L&pf?zMr=Upen zOrpMN5m;J0t>Q~e;cCF9g_!Tf;lJE&e0Z85MI4r*1xb0r)l5GQ;g4EI@y6Dq{-IHx z?ZC}HQ77(Lx`rz$~3sG7EUm|jH4%^b3Hzm(iWk`7H49$d0c5U(gd1+gR`~3L>5^pbHQnM%|W>fsHeqs^-{kV*CBH z2vb-5ns5Po`dyCJo&&{(-5E#EE${owWb7#B@XOP}2Jg0)F!CRsx$R}GS%zU8WLUZl z7T%9}{(`<_ya%lLCH%0*-<36ie4>u38fH=faXUe0p!9KD4?ZULb3Dydj1}N@%~cJ- zD2sS+;}CSy*d9If^RrBEh4(mC7THb78T-mb$8hwe(e>DCB(S|#MHHE1WuQu!xwo%Z zMN_+A40FTRwAe32h<9r@naSxnxydXPP!lBm0IGVJJ8|)Za^U0aH4bM73!1Ea%6rK4 zP*Lw8863d@4#R)=KOFe|&)XYg5Od%EySGrlT^Vp;LmEU|yhZrZ1wA8=J=2F(667FN z%-4Ed*|1hH-TomuZ6>koyl`7ZWvq{OWW#+|C#AjCp?gRhCCPWXZ|L3i1dr!3qqlkk zv&OX0dC$e+CB;dbnx%$yM%9mfmXODEcD;^wljIAX;Y7>MM>&f;HGf$_;Yb_f$L`#ovc)0wQ*yJT9EORZ^Wk1qsv%hJ(lA?}2 z%XNPCeLQkLt0`t97}U?U^8sgxMrM90Qc$z)kBtkVZOC`Un#xK`^?5n25Mr*9;S#rK zc?gkf>=V!=!zEJqVX;gzYUC_h=GC`DtP8K~+BxU8F<%o?$ULW@J|ea62;zA4b~|%^ z2DTMnOzi3>|5TZDt{0hiyT_KPf!8Z4l7*{}=q10N5V#{8%%{tUIHpSviMxLKNo=&Y zcUtrjdD6r(T;M#k;To&RMJ4a$lg1!c|B$iT_-l^%KWs7x@2s%)0|gIvW#4|y2^4!S zl&msAtS^r;UtsR2<87uhv-^1LE9>2IxL-dGnH7>4c^hNbG1S3~=? zbito?X-CISSm+s_N}eW|?^_>y=z4zJjc90pt@Eq|63Bh_G?5R{{_|a4xxV@Uf*PLCDJKa@+4c>4!65$ zeU6b-zb_a&i@Izr$35|yEW-?u`wTzF&0=^)et3dT+pb$ONt+|m-oR+W=+UwQsXuuq z#Uy%u^m#1Fe7nj@4+>R7f703)$={h@{KI2?a8wicERD*8wFY^f`1O8bSLaOXt`C>X z%-WN%I1hXNFtBp(YWt@I-gn{PjY2p*{FfrWf6%i4wbcNq_wVn=6F7lP2B7#E0M^k5 zwh5#(G~g(M4kXQi;r@F)^M0vh|L4Hefb|S;i398d_X`jT0dU&a;lLc(gB}J?NkAJ1 z=prx@&H~7x06_%g&UgpXA` zpos_W(tJOP@!vxjz+XxRBq_kPX`tnQ&r{)Z6aOCXUX%r{@IjruU(+F=d!rk?W&?vc zz(a6X%YQpgK(`W}d+_iB_~{SBn@zxSPgnCsd*GY`|GH9^!H;I@25UUsm*Dk>4kmGp z>*Q0MV2O9IJ$86SPuha*k!^$LvdMG{P7ASK61#bQya=!jE4Z9sSq>fEoOtm<*bf&h zC~K^}XsU~*`-{JzwdIa;)DWgYeOzqhzXrCAuMNlkJW*8*KK}Em$oAUgg7LGMBq zj|Idn<^TKz9kXwFpB>#jWy(@f%N}sj4cfXhk0Nu6)t<=B2?PFNW?S2&CBf=4YQ>7p z5&*k3EV&4(l;j1Ejw-bd*w9KF#njQq^{E$ao2V|bZfZv z?&jMWAWZ_S-}yeBbm78g z*ShcV$_C;Uq%@N?kOXyrJ$jQzA|DSzlTZ1txbYy@xIQQ+fbO0SBzWE5$63I=^B~(k zv?rea2h^cj5Pkbmr2m#afTUb;ZlTR8^u({F%3Yny{u5PeANAY1+2=ZE1-Xn|oHZ&M@x;TamI08JU$@i4|$Gr zlYCZG&|CJ-o?-X^=`bS-Ww^RX+`GJ)I@Fid8}Sf)=dAL2zQDd~%1qxv%XvK7;dKo< z{xN5j#D=m~(e60IUQHo|Z4zYTah{~^^^u*VDVct#<>aSe@)3f`$6(ftRqhgT>s-x! zJ9?fut2ePO;sw7RAt@mSQ22+%=Ghh{e5)uUYYn@!Px-#%FL&50Xp`mP^c&ln0L?c< zo!*-#879G>w2@nwXsBe(F*xzFnMuOc))X^lWZn9tv(X@_p<(lIxr<`1w?9p@nTFWY zGJ5z7O>FYy!&*0v!mb=!$E(-{&EHky5i?95x-e!uqNi)@pF52uuScW^CI#=;C(w(V zJt~MEJQFRCj-#+|G5X>0aV?&3ze|31Q#aWZIky}>+knXM0c?$$LN!^ELka{KtrT4lzMpt z7YEvbXlF8jmsqIVpCb$YQ} z)^N@trigKYS8P(P&&G+z{{BeY-8+c9y!C|AvHzvLYrn+Op4PR?PvknZ5RYsd9u!D< z?$y^v{*?k)MeNhK#rwvdX_<3mL7!39kR>~Bw>|tHwcxPq=?Uuf;YoX`8X&2D^T@xg zU&L6inKN0Q*;B-c>7WQR#B|u$n*LJUUQ|coz__ov2&JXZc5wX7p{s|2_@yM1^__4C-H3}M4p2%wAUJuwC zX^HaLG2=ny@F(?7@?8QmSw@Jk5-4!whQYRg~LIAZfrMls?BqFzX`#$O-LzW-CzN z;8f*-@&fFf_h0h_&=CblF&@BS?p=}h_8;JxzJQMi$Vh>Uz3>SuAaeJ9ruacceE$%9 zM8n>&2}l4yBo^SkfUl$g002f{?;YUGKzQq&Hlqx5bfpY=-0SG)y_04%;~F7tHY!d$}k z!i+@yZd%_mLWl3`kTTzb@7}g0R-47kmSLJbC~i3sfeEYWs$wNLuP_DAyA$0uw=n-R z#!j>ME7W7}=NVGoX5T{FaTHUOb*HkXzcC69Pa?lkp-c3nZ>wxcQtOC@6@xc)*Ir8g z1T=c$qC|%?GP#I>GIBlOa8fG53chgVQpd1}^0XFQ{&E?(2Pu zmf}!j%6P-7jvT&LY%PC7T@N^D>8|X6#k{ zTZk~fV48OvglDNx??Qqy8N>4lt3KIz=q-O~mj2zeLv)z=vD%wZbzS0?_T@fl`%g0a z)sqmz1|j(=Ou5f-zqc-=@&>L`nuBrJ-Si{urNWqI!go4vH}ltEy-WDfM4Zne2d@ds z_Mf7C$tHg^FR8}I>)T+E{W1*vO zWn1eQvu6?L7PIZcPG|TUqEVy7%|TsVUALZemFsCpHWVt(pA9MeEaQ@OKCm4){AoP) zCf8^tZvW*ny1%y{zr8MQfG=JhO*)@Lqwjb7JP{#T!lLuAJe*C@nM5Bl$FMk{JR`e4pU#b?}?|t&Oqhn4R@vyw zXEWnyPkvSI4_P@#yAkMl`=g}a6;nd6GQBo5f3wMC^#jAtxw|EmA_i^6Q9K-DgfPu; zeRVnhD2GV4C_6>%I49ti9f?{xr1-kOYA3I+n`HX-yY>0cMxERFL^TX8b~^xF>Cx%1OvwiZI0EaGpUc;EI5iY>;zI-AhdDXK@R|Mr5Whovxo^Gad) zYJqL?t?iHMVD!aP636#}^FjzF;#k%-5u|kp zZ^)2Fp=x8`z0srjQN}9z= zHzBp}Ht2g@`GSN)BK!5V{fmw-Y#C4*J;pAbaO0hs#08J=J2?s%x@=xh8EbqGLpk9{ zj{;=+n`!KWXF{$}G6&0~#-{3@nvA2OUsgOg2^#&17i4M4P%wx;=ZC^@?#c(1caSI)+)B|O&DA!;Yx9GJ^7;WeHQ@!>Q)knvPFlSXK4I8F$Hu7VG ztNPMbqnpI>)8;p%rYPvVXKmUR)7TunyP8q!B6MXjbjhqi@qVI_6q5(h&mBFi{8@qO)B)X;I&b`5mM(0xw3BQ$ZC2IkKu+YhUQV*7c;#t`;l9V`7?u%zg~}Q zj1(JJJh6TmV}wvuuwf62&EhEJqT@gt#`*aYv5Yi8)iQ-&>{+Df=ipatgPtC0^>aRL z5Y(cLmoI)vxOk}rs?Mc+hK$rGSIFbg5=3CJ^?Xi!RV@IE1(cBdC}y34Hxve~{vGZc zr>efr{??vO<+S==BNpk^B2y|xyUoRPw#X~XPoB3b3^4z}dif+Jr$~l4d$Natq2GvS zT9RAfD(5n+c|>Z2s_pEa%&(N6Dtp0#-r4aJMLNrDYBAbHo+A^R|UCqHq}!?zP-fK zRarwj6&n0%>1rcA+@fy>CoXq~{%<$&zO@XLTU@_~ z=62X92H>2X{!c*R!_o`SL-?0#0p%V(O~&$nrm-P3?DzI!_(BXcC+{s`_V=~k1*o@T zjDQ#T-`@Fy{RYV8vH&m(fE?iMf&bgxytnB9CaZhB;XwreXkPjvwFLlCp23HA{{<|- z9O%6$@Q=Rx04CsLEdaE+Z}t1Yr3t_!2A?gtPpYL!UxCA;dr26090N`yfo28#RPPT0 zL<+(qSxIEotNG@+eV4+tCsqA~{p8>Esri^X6^%_APP(L8*3gi!Y@3m`oU&)+-^so{ z{g?$J3G}X0m@P*_4cf31IzQuJ-s{JlE+Sx+9A0@+jw*mmUc3t7%nz&9YCBTD^)dMi z^7#wmojC<0hEo2IXy1jSF?;;j!eVg+A3N!TLw>GZFYp!x$)5pzAKnYpf$OFVaC1F{ zrVmq^8JE<_>qkc=6&_yYa znBp+)&wac_gn6lMDdsxn!16mHo@0MO?!JS6L3MX#U}x|7)Qy)gU2k;k{jM{1@1Bm^ z$6s!uJY^pl8moF(7qVhL7O)^&F!}j9vQhjmNUfu(EN|gX@|N~HOJSoHr=3SwdbC*E zsZqwAq0-w~4h+_q#F_4v-tEQ^fqdheprbUt-~xodAmvM?rrFy(@LKqi^k0zc5l$Bo zzvb&a{RpiLO^g=|tWhG~1vWM39Bh96*d?rUZM_(H|hPR$U%Ss!lP@J4hYt z;O&{4=0b&}*5yIq#%lcsQrx4>`|p9Qmw&Xif3PikjL9z!`4?O6N-WhiLW`s^&g=;? z_L=iv>pXL{%uCrXT+uK@E)yqG+@NjQ+nre)~Wz7BKw5ut6?Iql86OA(t|8AdA=kxP}r1IXRuz__kcLRGd)8iE;B zY^Kg0^^T6jw6QDSlV=D>ujuo~4w_tkhfvGaAL&GQmXJxRMD2Bw$T}^JDAwVTZyC9N z{F3o5J=g{GixWl~;;l8v*27|s`xx|F!BJ5|3Vn0S-17OCFHhohd^}+6i`eqeyV-j2 z`lB%Si4?_otMT{?#pnX*A%D9WjL4EXL+Dwtw?cz<9rkP9$YcXyR)Hfm=b5V+`PlW4 zdePk%2%$M6@ym+0HSg5>S9U_wty;%$sg7HX*{3r&}Q`~tL;-}ovV=9_@Qjebl=fl zVNw4}3Hjg3t?Eh_&o+lH65`q#c(0%Ie8QXa%arI55|^TA!X2>uX761%Gbvs~8Stxv z|0czTK21_@i2ZUTpIQwCIrt#HEOk@%{CtebC`5{SPRKE!m|L1TIz=u>^s0u~9TwQ; z%Xvsup;-1AA!R?jNMls5vc^Zup)SHf{4txlDxUcXddPEs>i5bVC{RCyN`G7whgWC) z`wKr6A<~_{M{V&?mjfQHP14aF3Kf~H*eP^RJr6NDl>e;GWt6>rWXQ7+ew zv!WYuXqX7Y9#g9hE!gSRnQXR9&j{0~Sy1*hp#6eHX7R zROGV~{!oPR=k`AqD@+N>^^GZ=0Y!SDRX?UOsJxaX76mpOTc|{nVOKx3rY}{w?t~A;$1k4oa5k6HAxzqWVPfA~H=?^|JlZ zJx+}?nFD$%l3xnQoumP}3G$XtK2iPp%0f->G-_isY%ITm$|l=IDV)xD0Nc|3(sB3P zW#aKV8n=fDj8qsERta4S6+EQOmXG849mD4!Bv61NdN`j~BwbRRUKwH?iy>%oj=zDs zdDLc|t(h+hG|!j|lD)MY0Q<;m+I}gkNa^F{5z$|93Te-rf3Jnw_vBi>B5TA#62^b` z+f~E|OgQ6a^OnQ8CI;YK3w06{DUMo>^HhnC-=`_CZjg4DDJgE+jNMJ{8izk=7oz$@ zT^$B#Ai3_rcdquKcR;X@r}NA$44dY=(Ug0!&MBA>AV(nCsV7lZLOuJg|I_tI>u_#! z_oDmb*A2Rk{*lS~r~*7$eV+^#K1f{HP$`D>TNsTyqXzYOF#iSlnuOV{nr53oPV`H* z-8yEB&$j8@Ze{bunVDR@>r+S8Us4F5Lrzwfi&VUOjQ)Zek;P$@WB9~#xC0j|=sdvD zzs1w{DeI0#etXgt<|&Wswa|7kbVS!db3r7(PJ2QI+-89DVxFZU`q450go@)F&I%+GFfRV-S=N=3cwd&X8eD%H2?%g#Rv@9!?(K-H~Syh^Aw()1p(%* z9()Uc_5sfal>{6bNa=w8;NM~eC?{}t9!>fp@Cf`B@QibyS@Iy8KxnW5WOLuq#`q^k z1b|i{Z~#XLu;Wy8INAcN8z3NQSP2MAgutQGea6uL;-C9wIb6j7Vgery1{Zklm7Y={ z1(7xY7~J|73Bpx(mix>6ugrr?^{_q2ziENWGBos2kKL6E5{^FX_4#S`Su?etp#C8s z=)g%RQ}`DBi&t9MzH1Zs#V<2uLhxkxO!fcp=%@Tval927qY5ds({6URCE&hu%O;MR zN{%li5kZVFw|v>`s`TN~eOgM2RnJemNua>S&srZ2B@uyN;XTj=JR|q;6F|hL=@AjB zQFbVlgLpMhyB5Bx-0f>mdD6-KB;mo&O-J^B--5A{lX{vbuM9gw)Z>M{iEz0l)Lq#& zXT^Es4w6b$tHIdSV329yO}s(310f^c#B{#XUx%tGhwD#g0syYqDe^svB5cplEdeGp z00a7nyi;&oS62TXZD@vcK`Xvw*5nerB6@850z*fO*t>_{D?3-yEkSE!k=`<2ryXKf zQ0EZ-8vs|>_5vk$4zTQ+ltpiVQH;m)u?)!-)Qp4^xqY+yxF==zSfaCONwF!UUW}5G zvk2VprS~X&OG87@()p%hD22-1xkyeG^_;r4uBN`uoXLjr=lR_hezdmWb86TNS!VB% z-!Sn~4X-;`$}qH^61u3P9W9f977PONnllM8fZK6+nA%WF4y;5zy=%e&@E)y))(NPK}I zU(k5ds}eA^uQ z%6S=H5S3$XDL2i3EMj$wv(BGtck3{ey244BGOAEDN^IW2*oeL$Emuu-6n4e_Cafq; zGf>F_WM^)RalP>~L$GVCh>58IK9k7snSl}fL!JX6tjtW1X%-`hAU&Gh#0M`uPAr%U zd%!}fnA}%Jmfb#=fNBzJLsw#j^y?nO@${DCUdZQkG2)xW(Vhge@2CZHb(H27*Ta87 z&B74_ZaXhHm3uOYyWlO+{8UB;Fu2}HMDY`qeK%hj6)Q~SVN)|**}o%+@I$(LZm z(Y=fJZ#q$|g7&fGEUCrX7V^>yvpncY7z66euiwWF<^Lr1T}M1Iof$6qAT($$Bv)YX z(6Yw#SRxmv>O$?rDxOA#!3Z(gl`vd6(ekDJ^bEhg%D$`8JN!KzYbc}aQsq#9B^zhb zi?8L(2?n7*P#w3lUS81K+AW6u(tBF-o0^0M3T54S&i?f%|M71_pNeg`(4kGLq6o< ztOjhlkAbna268RRC=D&drQ(#hJ90RN@L~u}a0OQ51zt@}YBfL7pMB&RydJAWOUYtX zw|{jqoe`z#6DK8t^C3-wv`FtyQCs>BEn~+3P6|C(0R@`%X`4xQa;u3!nRlqn}%gN-mdV&SsizzUFvE(fMm`z)8!4sXW3wl=5b%jr)0S>im4ZpM{jf_G&_ot{~ zkLYursV^t8%!O-BK{zVI9U^i@I9|lXk(9h9O1Ikm;>|)aWLVzjN z)!=#^Z+1qNQGlFwH2>&BdioehR>5@s6O)MZSvXyVddMeaXq7IDiH{59&_$2?g}z<# zr8K9NL5!eMM!pn3tDn@N3$Up%bxae0zFg(==FQgJ(OB8j{MI#VE#~xVeeHOHIn~Ch zSxFmNbc9<^Mlx))F}KDLEy8XKY!=rnBGOBzCuXjYvk5kU2_rJwJC^ z=6T(Y*2_nq7!(Z?$wHPQ1E0Jt+fxD!Sl1l3Iw9$K#Y|>iq1Qg+Gg|kv5o-_y*Q20w zuf%~j8t`%5>eQIv zz%mYDn^!m|LX+&6&1WUdqXXc`B(vx}-$1zI#Y%20St;^A_hVWpTM z6&|adW!PF9JGv*Zswf7%(@@fWR7p4lgf0c?4-6!tLv}yh`Bdg-bfv^Z36ZX7cGVGT zYV?mjUxJDsfALd#N$J#enm@5$Y*=}tV2T*4^9-v&bCd&%uJ6@eliOqQKgWsc-y4hU zrkj#%T+2SchOuucIWy5kDX0rjQ3c{Wzl-bLTM<;(qGuv#a!580HlkQ3*fcBLl^rs4 z6`)1K-;o3yu4^yJ{(^AX?reb8#4Xw%kl0?rt=Y4#`>KbTT+1}c?n++ z;<+%*eRktTIC-fd9$fifwf>zdRVl65lU|((-o%TQa769Fpj>O^P7JLv) znre*n7>G0zf*6jX-92`LqpACZMiIEfcBI+cNtXBmQ4o z>rWiVEg_%b5lx?Mq3cm=$48!9D<2Dd#eT;_a-E5&bP_s20Dk!fV;?Cnzv`GN`U^T( zOkdQ=%8%UUR8A_>71|gwLK+K~7nxN?7*zsN@x%x+*g=X?@id^U%yb}s9fAgjv2ghJ z#tgxnrjK|KxEK@QT!H-u9!?2(DzI?~pbl32+a3HzQv9b6_-`aEylV#sl>ea4f4{z{ zNx{*&Yg})h3!WUIOKJ)(j<31Xb<1cP(UN+!z7vu>+4jaIqMsyX zN|DM~J#9u+5_4cP{`$-@>!T*FjXqln>*#C|jB7s&MqyD19xG>?szGlD2{Ipq@q~i9 zS)}LiF3t^~#;b;7@QyzDuz+En&|e<~IZ%R|r)brBlW3v{Y0?we8bJ+yZlw>%`!PfW z@&4WOGtd?Lf{Dd=dYWhC97>R|Ut$5FH16{H5T-rtTfEZbA1)V@1319lBTm86%A5Si;=keiyb*JGz8a42>+zihbR z2n>iP*lo`_^!ED;TB4HqX__o!>10LE@M+$&yn}>}^aa+!)WzKbtk-hn_Nnc;H~QqG zLV}^hz_T|Y;KQMzA6@yi#D|yaCc;hUN&OmoWa1S);Q=n`XvmE@;2-t5ZtYlWx?jL! z60kekn_kAUJY8SFPYY0hMvMINUNK)P>z(~UnJ-zU2mZP1|3YIx{e30t-uh3 zoxbk~F?*j;9KxKWV86gcPhn}^=u+O}5@)FfDQAG+ne1j`(_H=mQ zX1req)8ujQs*gs|r@RV6oVRInJf)_lLXz2k&~bAQ*i`&sf!Y&qkXK887X!V5De@{6}P>qGP)(r!hPRub@;|WA4b43GC4YIAg(gd{X5zpVxCPa z-a0yLM^|`pK3-oW(x(6!fy<(^kO&H)+39_%4f4bmlUWXW2^_3bi6~eVo`?Q8#Q86YjHd4|5bZPy5B?4uQzzzV3=Lo48wlRAEh!3r(laWnvwYgXoLF zaeTs}n;sGDk4LG;NesnIxLv>Xj}5X*kJ2YE)d+0fSSGBiE%i(C9B8(@A9+*lQe#$? zH@jG~bz10+y|uJx&e(ZuAArhQ=T*sEn7R9{D4?Qv+GR6LqE&0IzbIeE+5XLi*W@CW zMa#US@5p>k5+}GXw&UG+DkZUf{Hb1))+-iCXXHM2x3}h}OhsJn=c*^IgiMewD^nKW zq%%TV$WDvCI6=mjFtXC-stZm+uGPT>x_7mx3=~kwA!68}gZ5!zu2{P|SAtH!2g1do zMd-(5t^pMP_&s(5{YtQL$Ya;LCH$2cl7fWNkr(}AB~i-+CO+X2(Y~;8%Zx#$;SZe0 zH(j*r%osXxNZ}%?!RB+V%`sZDgAGC#sQC+~LJk`QUDxP~98s>%^Powo%Q zDIcopE32y_M@{W0e*?1;3ZU2bR5T zsj{TjRnja=-U#U}3sZdAO+LB9y#`-RA1NEDE74TuM|s=ooax*1{c|#(l-5NifoRRj zjjYPd^0K=Vj0*p}^J^uuX~(%4!AyP8D*5VLbPcKPwDU53{qD_5$BJUvy8m+I`9Kc%dK zc%Wq=xCrlvF)QOHFq);h)IlqZgXj&pfF$?q79wbZo9I&SC2VZOA3zaaB$1b)Hkjkd z$4W=87_g}gzihS8xSvFY*mC*1{;Sj!r04l@qETtooAN8$(qf zSbDcNR;TkBlQ5z$Bp`xrFHD?q^!jb3=Fg9JCQBzSFggR_o;MnwC%Z_H#kvi>R1va~ zqF5s30?;3EmD78>ZQvjh_LPG;S)UOiH%A!q+}4gzMSa&;ri?1s38Tx6l;R_!;|G?HF`qldP5!Tw98cB$wKrM9#m0U_A^o(Q;dr@bOlJRXubw~Jnf zrR<~HBx7f92Xbc^Ac1BwfWt1O<#E3D4qo|ey2R5KpH?Zda6S3n6P^9&r6~>)UCSMs z`X(mX!7dtdqOF||;Zxq_=d+%{Hg8dgC`f58c2t&dWM?L`oR@Yi8$@*n5IOC`nGZ9@ ztBM#+$`hP{c_|&X=T$71uDNkAoVK>HiiqA>Yift^R1z>@NugWFJAJm+T~y*1EPa&w zQFIfoCNuRcel|I?Ej8R7!>MuSL}rAN%3dSQ>1&}5yc~@=xmL-?pNvhcd!F2ll6*s- zuN`x`q3_%?`qAao`Z+Sp@eY~i&5sPG_K0fF^kvs$Lqtb<;*}i@t)TFVQ3+^pZ=Kts z9_&>mDWpW#L(!Jd%FsWutD})V5Ly<8b|GdmJj=5Jwnxb$+0GjYsf;F0iYhBBuw1Ym zX=_ZunD>Al*+agyz(}HKDUdag{&A>J{KHm8VN z$^A&Ttdlh=S)z#ZMP^K!^e$k8aVV1s9>`?>IEh$Wd>!Ye-)5s4`-0P9VVf_Llop`CrODK=NU#xl>kjT2_>}A`}Es8=orzhWhnSZ<^Tz% z8K#OmgCN83>TmN`tRA&?;OAOZ7gZC6oF^S*JI7V>Yq~NuNRd@kO~fDGK^ZFVbDo86S|UzA5#5F0d%;&&^lkG};gcC`Eps8#q{O!`nT|yJx8hd#nq$ABNQWr zoJIMw{DYOJvW>f6oAy(4>>bL7*ksO1c*5HW^CN3DX5CrQ>#)LX5L(A0p|xc=Iy{=Q ziAw4$SfN&lSiJKZ1NjjPOIj}TuZN2}Rccjj_p9k|FVmMyEQa1oQA9inD+EuGjT{0K z;?lsT)iZKS`tYejMLR$v_J5dEcu4z;xo@h+1Fh9)c2LE=WlJ20iUGz5Ru%2A*-hdi zhC%5O$#hBWd4|@lP{q8{r_oo+wL&`VHN`FmZ7xTCjLWQC7e5xgfGl;-TaW#6XfCZSLi?U2j#{>$fKQ(PwRr*!`>;$?{(7<*Qb! z8{v+!AA;z_TVb?g*_~X!Xih(Vzg9)7)Se1Dc*{f}ZxUOk^5b*n`VUD80$jB>t$TI{ z7nzP{1{RW;vI|bw2P)yp{%VXFCZ4lL(lYPbEIumX+xwp4bY-JfBPkpIq!31#FFbcK z>Eg)^vCXp;R{PGVMd|ZY0QJ;cKWnjN)%raZHG6+Idx-k=b`4x=1T6EZ!3mFy~?N8Tz*-tDYVc9E-p(IL8EW2Z}H*M8A3n5$lKG+)O3>$0W4w!_|KGr}~dC2Hrn`n4|l=JSc0v@fdlcW0=DE zq2>xZdVh?G2drg47ABBU$$xfhjv>EvOT&*1J0oqmRKe!FU@nn+Li%n??2pasXok80 z8X~lV10~FYGj^I89~4BKf-$dbA1l?HjJs*GZSYPKT}p&^og{o76m8PGP6I~8J+yGQ zbh~4C)3iSh4wk=_O4$Y{nJO=tieq06@t3sf5$u6y1q8DjJcPu*iH;177CdoAmTbqu zU5!5i_C)%%0)_SI4q?p&!D(Fp4mdE>k#uG99V{Pwk=Jn`bE)hNqy;tI$#UOqd7}?5 zPaZb~OD@bP@Tj3%!;~a&{g)KOpTU_}<;1NlxOOC{JY`KM2w+L8`Xk_-CGR43C;3Z> z8pIaQrxc!J*Mq#ISu6`?V(6{WlIv6h*AllN1%QVQo8FOm*V1NHb&&UCZo}t7R~C-b z<_ zAY94c#Qfgh?>i3oox55KRS=nNuaT>3Q!8Nch zF5Jsf*&xwiU^$(U6>yF#BJ7`>x_@49yyKFtfFr#iO6FA=x-7|QP8c(Jb0dT@U9;WK z)W!BIfG6i5Bl@s%M)$J4FB^4IT(QGSy@=&1I!AYGEgpw$S}UtnZ8#w@B30Z$Vl+fu z2?~TL8AzFghY3w4c;#?7v^Mi<1o-CBIxaY7)SAASiP1wfya;q`lcFprli>~f)YWD` zJWnu5@!`?clGbYUSu><$LrBu4T3lyOR711oI#Esj z^-oJ%6bUjeLTRE2Lg*;Rx$B8ZS!JrK++y4r0Y|suw0jiopl|rN17=zgL+h@eRzqz- zC>H($HkDJiV$-%ozY*^~o|W)7m|ch#KbD}N;Gg)wanq>c(6^mAAyQa(q+(ZgBMN2Z z4X>ei?t7^!vP={#%$WS0O?tzBH4XqP{;lx?(@RjlW$xHcg0OjJao&@V(;|Yix*DU$ zvck9NRl{Yy3Z%Im(%mXouDs+*O1k?21C?&+`N2I{rcBz zY0E0Z#Vg7{`kycHgEjoT5g;<2czxp{9tAoLU5YoILw+lzUAUZIOh!2rZ7IH%rWR1@ z>8(V7a6h&VFeMd4zTlhMLkQ-(b(GZ5*T;XCM#AX`&oDK(air^f0)aTRg%YH@Txy*tck z<|4%V*`-ZC|2BrnF;%z&>O{uPjrt=+D9e)w-t~t0_4x(0!Sw)449oS_PI(j(WVik- z!`HL?MMPwVLxGkBXli@y;u;VO#SL-y7cZ%^1muVi)7Il#Qvn}&0Q+tACc;JWNbsha z`3XB)(DX+E6^!-T0`si;U&ra;R$;3F`u>oO3O%)rURolIjdg-sOWo<68$RW?bg^uR zp({j=Be)$OX!(E*+qk4xC&ZS^Y4RsSZuho5#z~6KQ!MTJVMacA?dm?37JEM?Yf%tA zOnG)Ro^LhUq+!swvL`O0$E%s25RTb&wN7a;3AQj4t+jvWrbS1LC7tKU$(PI9Ha+sJcgS37jh$Ez7RZA;dZ+m9n z`B|jIDcX4D?h+?21M|wo#d|sXL3C{aw%zWFkun0}Tr1>T_pzzeq!p-v2*smMdYo^w zRJP5#X%f9IhxwK+18^IK)C4 z4bz-4h^XH2jiP_6G@)N!Eoq#jCb1;6r)p)h8ntamt7o71xC6I)dDqejN;d9HTSN_c z@~O$2epkGU$Sb`>T$jjr;dMn2aqhPJxM%as?ELRvg$aL_rX0h2u7LEAuq%-+GL{m( zrngJ+HsinEXmyTOp_TW~{*fegk16l3i~O$7{fC33h4Nd)%Gwqrj^O!7dH`bS>gab5 z)I&c+o~@nXH0xvDD}6h`6YMt5btNN(cgC9qie0Z;u-`Cy(v<$hZOKV$sM=Qy{UZcU z$r5_6#6sSvcsJ}m>L~s>`}inuyQGg3G!(jT=O5-Ccb?u{Lo&?98Z@hNX1u3Xl+HXu z9#gDxw?@*ibtt4_VvxDPe*}T7?>{ko&C#7HlCOvNCOMVLwlJNtb^*LHE6YV)FS_;sy zS}Uw&=}8E-8btZK$Hj-&w~iJYjlAG_HBX<1?~7*qTVw`g9}l(6-ftvv9`=(;f70Gg4_g)l#)+;`I7p|R75(^s01rX%zU7#ymE{;tPoNcUq*AgYmhZ^1 z_NncbG@Vv5tAMJcA@-_mHO=gj#3POS>i;2$%PAZ`uof6-!RI z)@~j)GC?Rf7>PbmJ;~~Rl__6VHtn%Ksp4CG9mTiV-bn*v80bB-)~Z-(H@cjkYP{VQ zs~yqX5%Yacc&(fBr|GVjSEMw|c8BT~uxIho`EMPhNX}&1PS$4I>OCspn_l7xX!Uv> z4feNfr)kd(wdJDRACxPfyZDO8(ja3T_j1Vu_bPwZB7g|%*0}9=Qnk92M{jF_Px`}< zS&#cQ(dkwacvnPwy-q0>Nq%7pu^oF=x?GUISI~}bX?r;AYQ&$~Z#&9Wp$DFD2p+!G zZp7Q%D|t|f6?pRq{n*F9wQ|~Ro~`1UnoHLfT0&)tDcQY8L0cF8D7De8lGe_|{h3#4 zsL46>#bqdK%Y93WZtZ>M*oqxG+?Kp%no*KNU}M^{8scDl)KjpZln=f5lT>WA+s#@f zm27;F#|!4KBzt;Rq)r23OZ zMum{lQ;6o0y<>!Fp!KE4G=_j1j%lQIr_)C#tuPXpMLdl6>qvUym{?E7j8fu*K&&Se zl%!&ly#NGfwI(szoyp>p6u?gO?$mt<=}F(-feVglr6( zX$T^n(9&n#fdFzn>A}5+tur*Ff&jtK)_^bC>sR(;Y!`AWe{`IRmgB^_oyY<1iVR~B zfS~!yfeA z#L{l{qdBGm=8T$e98!+70B$*@2l1rfp4p(_V+Mg(c35-8C~hgaC(@c|89Y}LLU%KZZfNv2(>VoPIP|M|Wf6XryIJxJ+lEbU7c9uA18R3%JCP zOp+#c94YakVev-a2`RIs8X|!n&I+ zO4Cq4*3+b(qc|h*G_ETbGf9(*DLJQbu(_qpHjL46Sa_txH#B6>79ENY6y3y8ngYX( zB_?Sz?M205)MBG);z5^@6nn50T1{TUA@f|uPokWk(uT)k_6;G;V@=|V2*=7Ke0nZF zooCJB_x}KvR>%jdxA;`^A@ZF?1lJWOi?2(3f)Vsg{{S;pr12)FCP$tSAMT(O`H*t6 zzRg7QBq&-(RT=xny!ZQR`3`>BKR@kNNp&lWmn}T9I6XHSPF;hP?r!``VQL5r(?6mN z56tmcSAH$LnM%N`2zay7?ctDZ0DDylE?L``p+e(rBS_8;LSHM4ezelqJX1-}wPB$HlN=oOtm!86T%2Rlp$t+DUu^H(~O)$4=)P@_6G@FiTGe}JY z5{J+K09u?+PAU~V)PkMLMxDHIfId)t>r%^8y0gk!OC)RDDdMuMhnoQ%2=(b!jHw7B zCCd(^_O5AeY?g-q0Q@Anlu8VjB%aIVjQ;>Cjr>ihNN_G3f4Tsy0lH@SoBCAymOOtE z0;REX$Fa1!?xKTxUbjw!kMO0B#d?l%WLzGZKyz3O<=ZWcQVh>(wqZzVg%8uga z#}^UuB7sL>xo_~Qk@)`oZa2NW5<%S*ZEw#t#%=`C88Cke)j-YbvFsX0iFEB??1EjB zV-`N4SX&;Anln^1{aVXZ zggvC{p>8dm|-Qf8MlTwz5NQE&<08yGKpaP00paP0Y37{-36qHjHg`$#*0>Ei=Nt!4N z5zKN?eOoog_{!m8zOZIy0wz~%cILYWIhjZIMt?fte0D$5BmV%dL;0GQvAq>}G<8wy z+FX!_O+wr5_mBghaBDYK)wK4~WQr^J4AMqbRU8s?#yGD#gGJP*h)WfW?;9Q%MsR-` zWzL;@V}`l4nhc&D(2AxIrjh7g1#$g{Bs}kqQX4hYS1sVFl4~1>1PK|zb@#<}G?Oxt zw1ki4a-EMNrl?s-9B@ZNPe%@!@6wou(Md%Du(TRVC;-hA8cGHuFhwlKfl$T^6DxIW zV0sEO=SVUBtxiTenty6T*i+x}qrc-p@A%VRML<~d&j5EcToSm+CGyY(<(Oe{{OJ)k z3}+;qlj}`IZLK4myC~1MJ*rRQKUxO#&_BdbKJtA{AnHD}Aq1{4a7{8rU-fJL>7?#I z+@LlUR04ie_)}TJF5}Q4Gz?MDf4f;8B>w=AG@n)eb*1Kr;PV-Q0WuFt!SNl@lS(S3 zS(pQ~5!SP+{l;|Q;@xPo&UL~?-HUsF?A7ehxWpg=a-ZQ|C#*b@-&w>LE;!o1?^mfo zuj$cT7-YYVpUVmrIXyWT6sgH5E1Kh==U?4M*#7`hpYPIcZ#G^)QI0WK@#~PSuu&A! zGPZhSJp0va?N;ho=8hX>Mv#21B!Ce~*QSi4gslF_gYh_i>rW%m{{W?OJ{xVi_L_g` zy65$-uUeWNRKp&jaclmHgfa%_w>TYYgm-!kj7f1B3^GEP<2ei}ezLkNor?u04p}=M zqrE`MAObio#Z;E|*#ePs67k%FTnwHgwEI}MXs44SJKeAc>r{W?D3PF>%d?Fg56QWS z%}=(Q+9byT1ik5f$E5!NZP^Kd6EmHh>_Tb>*C2J0G`E%Ve~1Cc<6dbmi8Uv+h``as zDI_sO?c1(tmpaCyJ=9XI+>084xm4g}b?Hl*vn^pLE$+`p{@S@Ph~uiWfd&sp)irPPU z-c7HgE8Iz}23XD<#r!_pR(tDq3j^G><;BEiO_sr9>G{{6Xxdft>kt_&XIM<1dDnme;=LEe07sx)ZWs)}R5-;w z4tkP&>gNM3!?daDIPX?3w7)(_llwx>2-?^W z-b?ciov3SlIxCnD_SL`30p5y^!TtCc#XSv_yO}Xu%^cB4(z_h8sV&%^KT49;Vna+0 zR#(mst}9L9(De~+*7uUK!}Ar29Bw0ygwznv;mB;-7i;E?Cf&KVjm_!CY28~zs6934 zXUOesE#}ZcRo+fL@lrybP0gOr2bh~jJx3?}D?;;8&`kHz9WrTT+56R!QUDqDq>Ej& zmg`PP^h;H5FZX5Hn`ozZZHVSqGqJx$mgIl}%e0?y#d?2&$NW>yat>t&kD#szwd;tq znHno?J}CVCpe#IR)Pvr=Gs1H{&WpZMg}lN(MGC)=q^{nlJ|+984itK@CO_8&~T|3?ssu$670D1N}*0549Ww3wV!X{nIV=L-ul=|viXD=f#dMaa!sn=x%sao z8T7#Zm8;>~EA3+O;&X~mv3A#CBZ^rvJpDdli0v+>r67M~52@WvN z73#vOlsS(+r6(C#8$KVfl50sW2O$^msv4f4_fo1i4G_R1JXdLOgG%t2AyzWqa|f^1 zyxrW%DRv_)PfEgz_Oo(kvtG`cT~4D*xsLY4EW;AUa9AD>)K-Rx2b(qKprb7di1q=H zqp3B`+FZqLq{)Ms==ywORk@Nz4W1bKdQ|p0N-~L9k1#G(3Bru`Z(7gRwI#5Z$&K?M9FO7rD=YIp zNN#P*r!yziW*1B?ZPzd6jO{txJxKnwtg#RlCx4V+Wr!djtzr0Y%!%6lFbLs;442Jx zQR@&nHmZtcWK91GANm5m4isXaXTmYn!l)C zHMODGt+@9G1XR&R3~O_cR~g9YE0FOvuy+XH`SbUPCkiSeoaB(#b1vrK-)oVHhGRv@ z!0YZ!b(T=?x)L!WHe3VRxt|P04xxKH?QWnG?de{J1)P3j5X-eX8rHM3#KJGXeJXWa+6fApj?ZqzTKoyt(co?W%kz~Vd3azy=3_M2nMv z0LM|BRZ)DKxZSjk+ynB`wC=N_vNOa+On{6pwF17NRo8Ke3pcN);Z!D-MENKgldr$G zwP~Byg4Dcfc_;X4x-u+Vh9sH!`Imu3hEQb+HvrL@PdxKh?V~9uS%7oc4tD#RafGY6 zi43yiFSDBEwLM=@7eCqdt0Xrvox5?hf%={*qZcg^Em>Wi9qsg6-rLL#GY^n@*9UWH zs_NJ6Z)Y3I=dJ~H7MdOL5he2_&v_em3}?16TjJSlElgIsx3I|IAJ()|RM@qew?i93 z&|1mfeMadeyDBy>8BjZc>sGI>F0NH0w3a4Z<<>#|9qBD&u)LYCCoV2ijqR08j<~ID zGTz=sX~;%l)1_r8w=0zxsH-Cz>~j>BsjO3oRP-qrbx_puRmh#E-7eVs__}1O+&7>CX zCCi*}T;~9ORm^I7^l?Rt7=_zE3KQqZq@^vRu~xcGoZYUArrB!=d#P$N%^Xe{NRJ2z z=jJunUhA4|^zq!sD_jI0yH-=MA7PG7a#2~`%dDjLS0_#ka<<$_yX)BfD^FN4+oCfx za+DyXNHAFYf!o@(sM~2bav>GY+Ez4m87H~73SB(OIVg+5uWX89>5#z^OLXx^a=&+J zJ${`k3tRh$@?YCkgRZoUhgN~{3_+e^^J_JY?7nh6$BusHwXPHNf>(`KPrd& zMkdY)U#O+19jyK)fmHc1J0DlzRy_VAzG8Mt7t;%jekQPf*&84Af|_n211$T!g#c*D zt7;P*HQa~THb0d_ZY~?|+(>qj8@i_%;YMi`H+L}hOI`qfi0p|}82b07qJ^mUkiK~&)C|NPooFO__oQM?Pznxv^rS6GlhTl!bf7E*pISy^ z^`j<~3>>kb32n$p12Fk|)$^!Cs&*kK)bUwmfyEn#VM~_dO|1zgl*;>!L`@yD)|yRK ztR@-9G-tgaJkg(iDTqTxd7%2!O#&P}`c=3iL-WnTj>D;{ihVIo-g?q%&}U7Em7rfaAHW&<|YGN$fcHsP<@l%-8!|2)GTC+tQ_mPqY)X=ZeI~wONMi$`~gc zbg7jwsT%SyMaJ&mtw4*rB#@xiW8Jfph9f?lRDN@F^PGE{6*trz&96OR$bcbX-#Pti zmBra+CPVY~!Rb_5P!l%(vY`$ zXSk)#M_l_;7~sUzs^>jwzfPc1=Z+{Ow*}qBr0YGb&lIcOf6qTk({B{(Q=CnEDvW;z z%0EF^nfB(K9D$5dX$6g_b^T5WH^+ZJ9Z| zc*kjZIR5~6b5qOME_!xpb6lmDjqIi&ZlZg0-G|Jh^5(ZK^^G>>FET6W3g;OwkI>ZY zJ2fU-05jldjwk6NTWO$%NaBa3!Bmcc)z7a6~rGgY9yhT@)1Uh5$LcA+ z@Ui_g;r{@Czxved!smUOD}o;p#s~aMdG?q80If;?01F=r&YjM9{qO$(T8l8b-)54y zi2ftnlZVsg9-CYmy$_GHd4BM?kI*!E{XwCH(bZ9!%hL7jKUWR*S%G!xj5fFAYfCh) z0_KV+paP00paP08eJH`80*Wwbpa9WDB^Lq_{JSVb6?=cRQqAb@b;yAtw~2o0C_jcVS-vTRPvP6JdaCaqfvq^)NOu9o&;~|5 z>pR4fDtLbLIpr`hR7E|}k>GLv022F>zj#V9@5f5)6~nGUu0O!P@!@ni{{UJ)m34V2 zcMgZ|0sSZxw`BC(`E9=(e-NM_mmu(H>qwGZ@CZ2uiiKFk!TX2P_-bK;-!#xzD58o5 zV5lTxBbb!%KKb?b6pbTF@fJ zR@|O<0_+iMH&QEhZyYs8vNg9H1sMCk)`<>C0OK!Q+`b_ZFYhj`qhdB~Q@rP`WY6NO zc@>?mE@mNj0DvGq*{SyHqBU45omt$;Dnyxb0MAltk=8W$WR3^Aj7~uzI=AIqB;FRgyP1?rMz{1yZ`;PnjW;Me{Z8V=L zA1dSd)jN%1c-JosP@!Ff%P7J8E6;81F-dhWi3)tDX>N1Vj+GVF<$_zt5oU| z@l=;nRdn4#XgtKVn7-|xZ9O{F&!pWez+A%`5E~Cb<<*(IZ9C|WRV^(bxN|P6W{OmR1e=a~a48YA zm@VdtO-p|A4r76i207|0y@%nJgC0D?c`y6ZV19zH!{A>H!_!Pj2=617{bUsi& z&aX#r1Vi^wM1X&F0REJFIC+WVX7*M*WZvA}&*t0sX{3;m^D;=-$4)q_sTYeUA>#1z zqi1j?+ykG)*RJH_ujNNtJ)ApShm5P-^2E_Rb2r_JD{~q}LPvcxJfiHYGAFXSr z-XB`U@v{M-zaPdcDvHHXla))ibIY|L4fV+Yf5)ol^s0*vF5unB`y&zg8oj7v?YAEG zqxw~T8tU5}?3=I9(6&1p)W)`!M3T{?Vi-9JLF98=3ahKzY=t9&;Uvi&70B)ftw*Ei zvT6@)GZ7W4IcX(s02$zWn(3`!(=EK)Yi&G9BvNnlF8SVjk&n`*tmAz*E}DIIIZK3p zKT{HI%EyD-y)wm*_J*F#j0ng-m3ABLujagt>qw>6}lmCWsZ4NVtE)AcKhn~x+SovtlqPzV`44nLJ`S=ic0#?(gyP$T37INpB> ztEgUoWIQ@sUVWnCTyG;81m__7bK0&rjvICxK`^OM2lt4sDw349*w!&g*z=8RSCjiv z`rh1;Z1QA|rHJ>f4-IJ3U)-(4>f!|(@=ggggMA_y+X^GeIT_A*{OZiUCAg1CO9--{ z4Xq#;W9?neFOt#fbIqqwsU2Ri;xnfs+S|$j-?wl)fr{O?xsi0}V~LncfJBUP9CaXj z*P3YYTaM+d)F0uYMSNhZBC~=uC_5e zLv{97w`djIvHkRYi1)>5L#7L>>s$T%fIv#FS8vo*+LoS|4-t-UCkGNCA9}VSwHAtq zc`Uq|#m#c)XxF{ua9%vro?^C(xe_MHnKi_`^q(YId^8 z3mZqk+&J&fYoWJ>8)VwCEPh;JRe!p;{cCAdQKX|C$($RluE^(nV{~rqmJ4t^n{l@U z^~G?@YOV@`S+?ip9V@2S2s6w)z@&}q8$UmXtzp-e%LY2k#o`!`)LduD$CN zq75lnP!ivth%~Lq-TTqtJ8w*&xnA7Da zIp(&!C1NJEYncdaADFf|KVGJ>e!(WatGKs95V_poaEqSiyOfSAEjBwlF|lLd@Hrjn zRg#T06}VfPrkf*1-ZytBsBzrmIUT9@XY97@!vv`40nJ5a3&jB{?6?`vTzl6a;;mO~ zzC8=bpHZbw?U)3&k)E{SS$ zX{zg*qlw);hm>`ZJ5KxNar*lxF z`3eH&LVqDldy9Ca-#H>f!r&g1n<*<1->i}&!x$+S2S*XQ3kZqv}q$- z$DE?@UbTEG`f;_sG1_@IL3izU4T1OpisTTZvC}lDrdy2T$`O?w?8dgD(xuXLv8P$G z$*5`v_mi%6s*b?=Rmnyxusg}C^xw$8r(bEh)RWu6rNtA(xsv3r{2%s7s`2Q$ZH>pD z=1FyW#Nd|%k6;G^nH)w-R?;Wvzf^{s@tv$)h9Ep3`u(}$I@$@CSUt7u=^ zmLe#mk_dMnGY`3&=qe(YH@&d*dmNRgjyz9w30qrOSiAXaB1A@8^RAW;3SG)!yua8l z*lw0UybobhX+9UV()PxtR*hTCMt8>6?r=NTO?4p$3m_^uFS|Gu(Nd&#g0X}nCjIB3 zMKqgKK&Av@2UZy;@uHvepSv{0dmYqQJ=pCN7c{x0#U?qgP{2CV2=u2OGe&rz1`|o% zoJBVtv;cFOY3GwpCX@JSfZmi)J%s>z&;oJwr*@?4(~2@b8fZXHO(tkQ^kia~2s_b7 z6n3COVw1HkA>$pmpaUY1pURN)M<3+V0HiJd0P3Xx_NJ9N_4S|xCp2VHk3mR54NL<$ z%{MgQI2fcNfDoU0H&Pyyfcsp0yZk3UEEmFdar|0Tg$j1^1&o z(tZ>ingCEvc|VmYAX9Tq&*w}DKMFuaH+37cp5}oL>U&YUwKsPZ+|UG>q{z)PJkn>H z0DgMrfzz!wc6c;r(ts9_^vI*B%_e=Q5WM>SRGgkNe>y^G54oTPBi@toq{+_%icUL| z_){ctZ17DO$?Zn~0tE!)o@gi!9Fw1FZ>g>P z=7UK=Hx0*;NxPnu9O8`rw8G-#bHz0DrOqijngB)s6Uh{%a=dl*qhKwXQ-vJk@Sp~T z1MBTe#(HL#?_!e!KDeY79LG7prt{wvx%?@+zs_hC$C)xZ4k^2aI#Ww^>(YQTo|MAl z)t7iBH)FPG`%$n&K`=Q1iT-rX7qtx89cUP(SF--J%&0$e^`!@n1vL7OlnH^sZsMA8 zo~D9$=9ZX%6YEQoJt@8D#Q-Vk>p&z7P4&2T8ttKMSED-e(>ZLKH2OmlNbPEg1~j9%7!~6@_l-f znrUzk2Q?cUp1-XfaaCXxwoNE*_@?&t_M`{efF2({l)J&G&#fpQCHYfkTF0Lqr7e%S(VqLBVgBIrFimdbV#q^Hwq3s)F40NIvV2K?HmqG0Du8KY8tp$ z_KiDPvDG5qYdW#+hlXFM>sPl5@U@xakzO`sanY5qMRa<1jdeRDRh5?NKEhrb^Qm#{ zGt{>VUBbA?Jbh%y4ED35p1W{I>x$O0@m7&|cCFBjzq>Hm{P?N0g68h}P1PJzMIvsE z)z{TXBlM^59kEDYNi?7nP2N4I^D#=}vLAt-X|oWqIrhaS@lnp%*q*J=;Xu+IfD;(# zD~Ivd%X1kA1SUcJYqidM*Ae5N^{9W?{-%>QrMZa@i}i?8Xt+ih>hXd=`qWpxCe?Kd zc7l1A%Wu4>fM&OJ-v(OTY4Fc;Ji__Z%}B`FRJZsS(kE!*l7} zR32dITz`o?Wd*dqVX!89wf_KE(UFAq&#g?ImCB41v^$;%^&RUzU29CUpUHhf)INXJ zG|Cu$X1rg-I{m|G@?Ot>aAYp3mhwrE0qQU@>0Cv}h^DyQ1Zs1^3^@F~YN@GI*%Llm zdNzGZ`p%xYChp$@o*W$g05yvKHt_r28+o-@pg2D)xln$m7!~tXw6`-(L`#=H#Jpqj zsHUE6?5x>2=O;YYs*JCzGWM}b_CBCr73g^>Z+#5Mo+6QbeREYN@mw$|Q8lvTp=o!w z|0c6|$T;@|;MJkN}bC= zn6W;et$3>4+%xkzZ}-U}t^WXQU#fz)ZmcoF9A>hX5xw2cnC1{$M#rg3tl!Ev35u`Y z!T>nve_Dn+Jwo;N`BxHu2`mrRxOjX~;uvidM}Es6-N021`GJ~>ZxZVg58vKPDD-zY z{V6Oox>(uf6z#3gM13B0Qr8n(Kyi{Km;;~Apz`#%LwVY)B!}QajO33_m0Q7H9<%Xv zv&$x`A-G1wmccUP-z4*0<+sCq4jg&Sb8tuZpJ@FBOQzP1{lZw-wW2tG?JWiv)E~D_ z>coN-P#pd=?MGU)YnWxVv|F$t5#n6@r@v0M>9*bz&@FN0>2~Cf?l&LQ){7H@XT0 z3ZMbcu4~pl%@6$YQ(XT5j9`s)2@0SGa`Qyrx?UP`b)$B9POiJG;>U51{CL->+xQd1 zk7E{e@L0w^V(d>NuUrcA4Pk=YTaM&${{VegscW7kxA5+R1cZmWk{}_7o=L}3?Mk1) zp?LQFx=XR<8nkoxC&X8FmJbZ5$_lj62O&>A4@MQ)d_M5ixv*V!SKoPXTWMfEL7smf zdTxi{ldQ)!tKv^KORKI@J;h1`{72v0wNqTMmn>`OZ>VJ6&B6Zwk5E6AEsc}j zYBrHC%09?O^J32ZFwRRam`VBA~}x&YxtXD4W#>NjBTo| znQs$E3`LAgG6q+vf2}>WZ68R5ZD*ES`;=^#hvftPoYk!At7c)-t!>`+IKy5!$9_WK z`&LA)soqMaSgjILm9?%P}oWqRb@M8&P= ze*UOW@~M?AF19IE*3WXrv1FE-V>$+&%4r0V6>u@0*{@9Sj^Fr3t@eTkJQK*TIJNsD z>C@>Xi4;xu&TwSwf$d(A;dK${5GWl99WjbfeGYtD(KK;>I0|)=5BDpQWgg$1bv`&? zy34aLP^dXI&1p!Ql#W~&<3LE~mc@0#N-(=QYA15AwU*?RjW-^r(0kSUyBjS(!;)Qn zr2Vx>`H=KJf|Bx66L}FIA_YR_)p8%u6If1e?_m+$1^XyFn`qkL59w3P=WfBuqq&uD zaWt39B5rxyLm9n@vHJzwxBJSy( zRQZ3x3Fs?%(RCU}I+r7I66WD9ZzOJjo-xy{Bxqzk4&O>$GR|#A?P7^XJy?!{y$aV$ zv9r@Wq*XF1<(U2K{e3Fx)l||%LJq8(IsGF~mI>mxDii^bta(3$bygb0YpDgC5Lj?_ z{JH2cSko1_f@!#o(C2#OhFZ@{&F)HWWIztn!Oyp?a!&OdZ0Si;R!3{DsV4r)6HU#$tZan0G{X6)^MF}Acoy$i@}&(dPDF7;xEo07TQqo*cC%EBq=5oqh$?bD1z_tl-P`IGM%_ax!()se zueE3Ez9MVe(Qg_P5XR&TPd)H!RYlFF9O+%LH7P9aECs9X-5<;)aq}m!J;gi2ejeW_ z)Gk11Bl(c>10PzsVWL5-OB-BD4hH9fJp7~I(zfndWpQf;Coo&d7`_&l32H4+koAxUdGnp?c%XVkWB6aKtTWxTB)Yk%-0Z4 z+|kVdB^gn=y90z^qyh0LVV6nH@DE& z3tsDQbEjmIIw@fYk87O8(6H*py!TMhrMlLIwURTIIUs|D`q!h!ZxTiuknhe*Xa4}# zPqMd$2au}fMg+4k`H$A3l~)IH=97(xbnPj0%{?QGB5hxsD(5HKtVb-XZduHJVa^Jn z81w{GxA)4n@rP22x10`!uBhGR|?D z$dRw2)ZJam+mnTJAz;VoJAG=o)XR{Ou{(If4u49y_BFVTi^>9ll2{Pu`Ef$f9=aA} zYf)?=F-I@65_cb^Q@qtJtQI%9k)l@MMyhh(l`WmSN(#koB5lv_=jr%TO1A9+n`IXV zegP*T8`Lc{n|kaPvL-`wb7pQWVJ2q#sWBrTT7X!*lS0=g&6FSBD8LBcQ`hjV+dFwJ z?SZ(Q%H&`cP`LKMsO+@XihnsGUo5s=-#eS>?e(J81$z=(>F$z$sz9m8kS;KOo$AE* zNd!(EP+mA+ zn6nH32d7T8aRirfF^zUb(NA+&8ZM)A;t3{`YegD9(A%=3{A;TSHYx%E7y|;FlT3)a zmD1hAEM-YhIo;I#YAG%tvSI|Db;FWT5aaNzH&P+m`5^J~)@`Pb_H<{$07)b#^))NZ zwvt6)^4|^Dk)kGkRAKyI)~-hAz`;CVaq_4cIQ7L$uv@B!dC6tq0nRCrG*+=l@S+JE z^3Tg3@uA6D!n1Ddtu6fTfv1FJUzN9l7#hLWJV7<4QKSxg?E|mlT9?y(iYYEwh533O zGHcJTrM%WI#83$glDEuN_HG|qDXw>}$kRlwShm%!{@$*$Ok^j7g|N@*jPY6)wz}S} zt3AB(-WXVA+CzYz-7?h3Y&6SD+iMwGNbgGED3dI|h_13n7EpcS7Z~K6{b|m6`>2I? zxj<>Z+4sAmmNjyLKh@<_4^U52R&67@o_2ZFq?!I-G;kDpC>27_P`kR;Qa|j*ngV&a zRv<6uT|m22GeH0dRWrN{g*oX{$kwt4QC*}q*H5>yD5=D)&UTYh&zGHJW>BEziQ_e$ zZ+CfOjMK&D+&d7Uwgx*IzXhAiAe!VAY-FmoL8UEr1*fT5BUJMvwLn$Q;>bZi{c2Z* ztn~TXD4C>QM*JL_(n6zhMxI)O&IU77H4QK4mee0CoW4tX^O0 zx>TQPl69WJ5AP%6a9>q8t?t@U(9TJ7H`vqu-_j!aB)Pmt9aKPgqNz5wrp|oZeMf9A z0aanhqP6y3Ov{lyY>8CrQ1pd z4FER;(%=vNy)VO@X}Q3r0uV=9ZAvgl%}v-iAkYIi z?MMf;DFkMWcj-WbG-iQFX@H>fMo8w9G=%f(MZj=uo|L&2A9_2`n29q-S<5402H`9(sDD!8RCEtifK3}idHxosP_|0OmS*V zifJb&J!t980u=gFg7KWwcpT<{`QY+sxDMx@!`6ehA4)<;JY(xj-a+d?$Cez^9lB$R zVPa3cCU~Gkn~!=%Y;*#dLX%8G!(bX{(}NSmGj2H~3RCioj%We< zy(p#wp1e}$D7 zkFT{f9CYhI3*L%MlS`gx03Ot*r63}V=9q)2p#D^!jRU_F0ISHU0047LADsoM40&(L zqb-wxNy+O=#yWHFK$-vu=8eXaIqONDfKvoJK9w0_TAjNCii{kQng%#^qdh4pGH^Ji z1J{#Br6Qgo-r)ochPz1vSP=_NG5NdK@1IWnrqy(O4JYWG%1eNvtDiB6TAC)|wMF3m2 zySTP+&`%_6r;Ut%{Z-If_~%l9Y?&gs1G{{!`HJM2`eu#Cy-SV2^qW5%*-lk%A(93p z0@)0|opg~-lSZ!-tD_8p4r|3hJQ2-oXu9pzlQ<|#!dvHypl|RsE+ai#d8i%9pV?Nb z>H3|%r)akp@0I957%F`Tr>pLFZ&t_U#Z7|cjrGTP$nTjK)d%x6)^6sx4;DJL%vfyj zy*{-vM$|WFQ)~OHIAg{(pd5P&&+#bpbQ_U?N`T)^YG=KM6pdtjFO4(rUKxMy$l1aB+|3YlBjo;x^FJNh7f_H!2%ExvAz~-DH z?|BMNHndfTt%=o9=9W@j22>njzrwLvBukSU4u=4*dhBd%#`4j|%76iuUJti5g{|A| zyl0PUD>ip1;0oH6OU=Z>tu@KaXxS;67~@YRK^WSc1O7G7U%=KEtsAe*MgN*sgJ6kXgtrFrJC3%8BkjcHCUGQUG9%}O@vQIU2Y*gWEy_*JQ(cV?F>jN=s` zvHi#)iIZ-2k&4@y7&tak9CK2vnc%K!U$MWI(jHZNj?~y8F6U zf5mIsV4gdeQfOUXH&DvTatJ@lxtwfk8C_YHH_^JH%^9U)$#>U1<0k(AihfKEcdc$_ zusmRY;zK9<%TYgtofv2P*q%|Z=Vh)xxPP5l@aD5(+S=+KBe=M@@=$psjr@+EkgEFb z40gSK>F6`wujy7v;eBc)*X&`Q*5Wa?L-VQw{6p!`)|8jHX^;LZJK^ys!()#+;C(I6 z6`QX3$HKbSnKiA$Ts}rnhzKa%h^~%33qzVUkuLC(bf@4B{VT^Sj%9la^0FdsV{{UUn`HIt^PvK|K8^4tSsvy+t)z7UP~No2#AGJV&O@4b_#k^lx~FYs94PUU}_GPRiv) zb2DlkYfNjQ0MsqK?BmNasA|aZ%+a=+8mx%SB;{3sJuAt4PpD~=TtWl}Q5zpK%WPxV z8ml_#D`JvZT{qjDji@9i1JmBKw>naNO{4p{=^hW@wAZ97 z+sAGlRfhq(E-TM%d{?O1Pb11w;7K2p!c^+MnXM^&Rcm#l!)B2@Wl3prcs`$oD4rg7 z@;V`jr)_sOygzrS++JPD71x%s1H6=0C+G>Sp`>5j4>>QzvTpZmjN= zZSLhD4=?P@01f`uYU;Q4dx(ygc9< z_8&o1wzDNW*z2z?P0ga-TCh=ZB1r7LBVnE2<6ePfd>2KHppkR4C75TQrFkxytifYp zacOtw2#a}$soeY5W8qzHJwnFmW;kN_1n@DA)x}aWhoP*e30WMEiDcEajY{FfVp!uG z%DDVIXFq%CUG|}KC5l{60a+Jz)K$RrNk7XSCRl`b(@?CQz zr2s5_t3_FIYK-R#%@~?AQT>i|ynAQ6Scb%yU@#r={p#Uvq0}XwStAn#BF1Ib|Hy>rXj!%~Zj44AeceHGSU3rHs!l(AuLQ z^1h$rTNiB%yF9{E9YD=<7L!jFsT`!3_b>o~kJHk${6T)`rR19sCwygzu6aW8NVwkY z#JQQSHS-K?S_A~M6VzkYt9WuCd`cmeN~;1uBP5=~=xa+zn%d&Z?&)GtF-?GO05w6% zzRpjsJ5>4&i&(U>N0zymXA&K_{3~ZsMKosP%&vAX<-of@3P|~ikMOJwOHjAdl38<< z`^a&)?yHujdzhWuV(cVs1e~9I*9orO%CiB}f(Sf{<)KfSR%cA5%9}=P+J(fLfV!4O zX$j*1a4BydO;-LhX%vk33P8am{uLIVVJ-alEBT$@hx{o9iD0)EZVkkW2~vLP^!nC6h%`v`Im5uM6p_Bw;E>+=sxxY{$8YwD z@1#3E;uDYuZnZXnX|&d?SF<^rsBV{5f3m_8c?F46f%HAAUI?ub{P}Mh?(qKr%QkJQpMdFF zIuX4D@E@hmYSBXZlz=AUC7MmgtdU@C-J4y zEG~Ti0JiE5!<@$ot^yO&IO=OhPt#QhxVc{{;kX$&lOEMH-c`K779_XusPEX5M>Um^ zHgYUEmNyezMyCUam%qJ3E~!1!goYN+Oh|Yim02w-`EoD|0B@b#oZ_7<_LiE8gjSYW zc}=n8W7e_Nm(t|3n2$; z3}-YXGA6l^OO2qj9u5vaTFuohUr@Cc;h9Ova-(s_dbwtj&g9*N-JYrsPs*=cwY{7z zCCh|`xIX^?TCJVLwYo(;4l6NWi(tT8N5OAX`Eyz@T{Fx3k+~D^JDxuZf+$7WuGd&C zhhPBxD&Lc8ArQdCAU`n&txLOiVI3BLFD1B=U8XS-^lGDXc+rvNua~*mQUD zf~uAIq|V)=*13qDYwsp7OTiWhkoA*3)HQMZD^q$m^NTrR#D5X#SvrQI9n5x0T4V=t zUf%UQ+FZ|QQ|(OzAaq;;eXAWbxb3AdK*Hun=uU7dIa7L+o`oys7f}?JNv`Hq$yQ($ zezkAH`Z<-B{4#^et0~Xnirci&5;a7aL^hMZYW?i?Ijf3~69Hz50)GmOFX>t)lCv|D zZ)2U-pu5m+q`ivO841q)p`87DirvsPO+UptV3E-vmOqu_X2B=%u6I-Meah-`>6hsd z+;*r)@_F|)$ZDP*)a@i|k0G81JHw$I56_D0g%v2;PR4aAH2uwAW7?&*NMTrEK^g6V zoK;Kst}Ly0K^UFb;Hv;Xm0|coXSUNLd*2XR-0{Q)LApO}#PeMZwb*-!XSkjcgM^J- z9Q*KVnyh(b;%Q25O7{5~mbQAV_i(-7iZnyHV@BM=>P>Xg08&IO1BLhlQYQ>RwE}5fIab0 z&2IX%D{PYk894bIoPBFv%SgJ_p;VhI2^exl3wRR>**vksqylwYbJ~Q}HT|Zf=+~xLkI~0hdJ!#}q4;ikr z5C)9XNjaqkfE4tkp!K4F6YEVY=}tYV4teH)BcmXndT|_6NaC0a_|h@R#W(s<+v`jN zy%c0GYBEo105SEWG`wfu6cNQR6Fk!H1azkNpz%O}w;d_DrSCx?Py!v@`cdgYB#Hpd z05yj_Ge{0QQ{Nc+R8HLSOb29A9CqtSI#I;{Cu%}6JJRH5iVsQvLO9}@qb8lrAgTa7 z3KBEHJ?LIYrxVk)AQMnyE|6q%q%cs}$4 zOb&61Q_0}aG1;c)o-sygzgl7+pVE^&QVzb9gyX#cJ9loBnWR0j)|yU7J*WZM&osCh z=9!*4=8&2MJhl%Mh0bzmxgEOwX)-{d2Zts4aZd+5>Jx#3oX{JNIG_nw`cy1==ZXt^ z(@D)R6Q_DWd8H#9(sP<%8O0rGcG4UQ1}{v}nr?DAq})OFphSoArZL;V>p?j*oM7}c z0MpM?OVgfdqMS_tAsqA+H%>D_p!GBW-)c+{Y5xGjOf(2WX$j(=jyei%3GGb))6~#M zYEnJv$}#VnVzAX*(~9xMDd2HP1F`660fbV71aNt$`AknYOaO$5|={0$j7>r4ld z4JXu6XP?4@(trYU#Tmh-)Qf=C<_V5 z=9B48#{iSXAzTiJPKU%=}AkDGfV{( z0(0$2$m(eNP%s4NlR)dwN@+Mar3dLi4j9cS>r5vHJq<2-^#Xt$&(fDC-kLF*c79RP zfD~r}lzs1NZ>2BengDp|OS}I7*HPyzJt=ZO3IH>c`1Pgl$28oE3vo$|mNw*5_m50e z3UV{hd(*S)_)-GQS5iRX;+5kdj7w|Nz6|4qFW(po-#!t0l1?1+nv}U!2;iQ%$B#MNtPs*$+DnUj! zTbjnaospH|Tie*~-pF0D#;dsDLaF)>YUhz&H$GGDmANgN*wyEUQoWU0P$mb4?Vmwa ztRfcE#|wg0m>iD(0F6W?Xk2o;miH&O(aYQUlD1@#e~9qEL0R_Fn{7^8Ekx`>`3&QX z*IRHFvIS6fJ_Q_)%KKGqagcQVFNkIKi82j^J6 zBZ4SmM^_HG`@nDy=TXUKyS%%3*$R$99qO-@ENs@&vdEGjnOL999@V8dB`#}y3<((G7tbFj9~C7j7vU!C>tgJ0BC*{?f(D?^=7;9+?G-{TGCCc zll&)<`sTbjPs@TUsqklr?)*Ed;sA{m))qvNcO3|*oNj}&K8D{$`t$GjOSJ(#FGn_Y83iq`3PF)2qJlsMpz z@UJP>xr*B#-EZe!t2U`^q-asy+_ob~lm#7F z0A17hitRooHej(i2bTVo#OYEjTCJR}aH%pA^r9<5=lnmB?;-g7DR+v^U0%hb3@#V) zAk}+~e%?L8EXaL-hNjw?Es?dm-i_7I9}+I!A;HJ@xT>ethQheQNp0|JKBH3fjVlwm>V zfPeb+En8MKcI=CuA=eFze`bVcX;h8G{m=frc_)aqKe58r(OZO;Z<*%C8LMMczLMtt zH)m;HL-QQ;9jlY^TFf;imRBqt1~c0gl{L>&&ttA@+0(Mz%!XxLJA)yr@`N+_krN+r z+Xk8DG=DJTG}VwuPt2g>KQ($5Jnn0WG>(jS9F`q($)rgM=b`%44Ex)hV0RQT%K)PX z(yGMWonMB0K>9Vyu|V-iyIpzX+*hvYW*t(*+*+9?-kVZFatHT@3H&R-ZKgmOWE`H1 zJ6B!dUm8uIA-0_wdnP&Z(SnfQTvs(pN$PD1(n`mqc!o3@3r7Mr)KUAkTS8c%W-xF*LQA!eBb`6?R-5XI!rBXs7jt>?lBl)@_l`46!}_ph(sXBK;Bg2 z@TT~FDK!hok>FXpIcY5)nl0mrzjnl7N1 z5Km@N744X>3@{e5d|9i(r`}sY(5_^J7so-GrD<-uY=7DobAtqf=L4Jy-0ZRB&_th=5wxZ^vAryNxobpv^SW){(8h{?Ga`u_m4 zT9V!AHilz)a)Nu1-dHCEG21<>Xt^k;zNAelO8tzV6;BqV=JMLYP zuI^Rtu92gKd2OqbKf|!2Hy$pEDCLgIDI3iGGIs7B(&!*30`APEh-pjB~>c1V0)AcG3aWl$SOUJ8=Cp2KaXmU`$X~c+k3Ik;rLYzOH{kn zZULS6=oNYju2}8BAd^x?K3(_(^vxWRi@HPSlUBKQ(@~Pn&Fzs@Rzf#1?Om3g;+q(t zj7tNCz+B^Y2d_22?oa`7o_HCjyx%AQ;CfbdC`Nmi8m=dzP2(*h+6GuRu^AvT@W#0N ztwD9EjE)i&8N)Eh{HvdEWNtcp)s%7OAN91H3o@WQx1C@t#70TH0J&6~PL>bOG#fisG((ajVU8j>ZkK9Hf3w+P{T$M-Nh> z(88riLEY|p%(m-e6Fr;=BqKNn=63fr7MZ48$3LBY#H?}@F)E&!u181sp9{vYYW77~ z@MnV7D5PeehjEy_t8cKZyjd6mXi zF;%VY8XJ*uB&#jSIP)>U{OdWd=brJ_BXZXO;{)!-eSPX(Kx;NOwuaNlxtZAS$j;O6 z=}4(NyLLKgZY|fDr4Ki*I9h}3DRFqlFcdJ$IO|DcWhz;gnYK?NWVC6J8@*+=s=gTW zCBC>f5=t}40I>AMLW^$2rulA*dX0_qHNCaGF7xYkiwSDEcYKUBOx1*KAx2%2^E?jHu4n>z%7iQ#PdU?xQ6253*equ%eUWN zp6B^hD75z_No7j^ThaN$Eofs%s=5Nj>}V;;cYy| zZXHrWsB3IIB~lJCS2M&l6sFHl7ZZ4b!7i>hM`{M=45dLM+o`ANx{a;GsP>abAXeBF zInFDb)IKO#+<0a<>>`TZ;Ek;VsbHhOJaw*HOz~VWzWb;c;kOSUg;VtdlS0ByN$Fx` zUOcI`dj75Un~;kfh^l(?f-3Kaw4q^TJfAd=Dd%@2eJjl+)gti(>w5%(D4<|k2(lTm z>&2C=mJWv#;DcQ_6CfA#8NL8oN3E>q^WW4gV%vVk_t_&NKa)&e}~qr-KoG_fpviDUqIpFk?Uz404H5KCueXK^ftrS`&?W+x7Q6Flxvwn$(1~GCl%+HS2A4M z`O}7uWWiEj1e$D`?z3~J{ekag!+!#vG4GG-U2>x*6qc4b`PV)AekY=8Ha-B4=ZIK`^2A8l9_I$KyiuqnzM~DiO`Cgv zz9GtwZ2Fqy?|v!??HJm^W4n}Kl5$C|F8f^9XIGBmc0nP|(x5TTVGJEd@#*SxEJZl# zwSGrn{{Rv7JvChmLvRam=JLur3e?iRAlqw2W51F}WCUdvJgNM-uOwHISnmM0Qe0-O zSnD@hb;zF2WHPS-f^@E_;AuEoDO}|G+|;g}ypL6z$9k5XJ zo1JRqOaITvoPmpw^MtXTvC2DiLlZ$Ns4i# zpadLJ=93hkN&t3$Drv_wzgk0#PybQLz&}nX>CYd9 z6uHJI0SP(jNy(+{)KZ1#igy9HrD)lQ98%`^rQqEO6Megbe*YBPAJDd)W8Q` zl>Y$XrXIa>P6VDiP$35-=hCBP8R_~|na4v+&T&iz3I=E(gN${g>%{;s;3*FuN&x19 zX@Hzjo(F0=_M@&lPy*)*$5T!3OP(=J%?tx*&q`c##U?N^X-6F9m<`A0N9#{4IO+J) z0VfBF1SbNJnhzrxq{nIiYjqUf-TKqW1Y`P9uyw@%L_5*ep38%pXz9SFA-KgEp}ON7 zica*z#U_*b(i1@Upc)SNqoJkdleIJflZsH>({q#0=|_Cf0iJqyqdfYG0p^UH@jwSA zhVRWD+-8#;b)Y~oiYYNkf+zucdQuJWv804)nq^`O}XrNXw7@y#f>ZQM;NkgGt!a037tC0|%NY-FLF26h@t_6TJt#bMrxUjr>qgSta53#b z2!wYT{*@%1dhyzk5H54cryg6N#Q;aUamS~nBXr~Sr!Y`+O~^cAfEdNNr(~a)nlVQ1 zHq{a^j`Y0bVvcD#&=wazg(f-XkkQ_OSPm&Ysn`aaw=~2ZsSP_G)MKB*fDnp1(YG0( zj%k3DgwwlG%>VhQC`})$1dQ^R>z~_npcsM*_lpd7MNCUMm zPQ54r;OD(A?wA<*QWM7<(*ZnC1f2AvJn{I_4EoY_;(!$AIpTmabImVNkLO2jMF1%{ zA4*mArel$gY1@xV28R|S6&~zo6cd)mG}bf#hm+czm@$#fHEKj3A?-<(9mw&=T0UVD zqOo1WX~CuIOYKRks}3&OnSVStM|?yvU%p3gYPDz)ZPMc9L|-xT_w~hFbqqo8kG)u$ zs@z+|6tj6`aNDz3I#+fz_KT^hE$zHF4E}gvX(3yIDfyC4KnI^%nJwYFh%!RkqHM8q*0JpF zRwoGKJJ@{Qg1QE|`z4(7LZIzj;fTTK{P9`NE-uL05>nNbE`C{^QQZau2F7t&w~?Tb zyx_Jm`{n14e@dqgtEgSHmnk7xP?l0<3ZQ!_tgFlCk=QUh{{V<&9)Akn6k28#D@T^* zj7|1?kvvZEAtYrw)k~YEoxWW0#!1T`#-W1orFi3$+s=^|9%oKOv=QY1AwP!y! zGSfo8l_uc0JG)kcTe4ZJe;W10HJ_ss^Dj(OvuSL5dNH6O|srhV%~?u?q$csLtQXev6>Q?7GTFz9>G10R4fR&1Mgp>LDOkaQmP zSd)>(S=0E`t&jU+lM(2hj~vi@_N4XCb5KuqkfR;S%m>}vD>m(^tW$dvw2F(CWnXe= zTj&`i26)fQ{qK6_wJWI!0^tiQa=wT`jJk6!Dj4 zJ4wI`L?O!*6>ED*HqA)xVpUaHlm!@3yQNmXSZ7_{Y6(pR&N!#u13;9_FF8;^H6o$Md3FZz@fkA4A@y2V<*+8Bz~U zw2fZJa9C~mLmoTQ5X5Jv)}pqV?rhe0(T+w=260OF&_cgC5rzB4LIoGGicZ9UK-c7?8(4)0F3P%ezihhAL*$W5XCbd{{Sc-){VidZgwwuJdy1?O8SKW znuswe$Lm!rF5}c}BfPh4#UTD7G7U*H{g&14=OR0F87_T4tt}AK=FqmVzgF5a)0(NO z&O~c(5#~;(s8L*{{{W69wVViIjz}Az3_<+sLruSX=;hQUfFSb5BQ2a(72Aw1ZAL4e z!px~{DQJk7Y$8b=t5GXnBSl3%{VY?E|`_zeHFE5tZR{RHQ^X*pq z?NgLczEU5P3yRAN8_NBS@e<{UL#|YwJ5&;N#}zoLMnQ_6jZ#sC|*rZ-Y?OVDDD zrhy8lCkCfZ8`IvRPyyvZsW#fDDl#H)1A(z=ZgR*5WH%2fnl?I)letCF-DSBcd|qHmH&v3}PEV)<5wc_Rp~YyJdxkkf=i=Hy~rEuR61Q zysYj~PFFFRoQB^Av;2@|<8`bU5u^iEn-Od?8`6 zhYg;U=Kd)`Ypu$%MjLM`cORH_tuSp$T#_ccafdS8uA0U7+RX8~#7k}g1_J#(DjS=r zu4Z{=a`HFcVrr3-&1@$eXQxlbrO(JYI6Z6ARO20wHdQ4p#~BGu`&ZVQA1b&M(t8p> zr5Ijve;T3({HjRWFla7>uEr<$j!i^Z^y~d9#I`9GpB=~Aqw^Vxv;(U9(z;uf7`YMe zAcCiy{#69KjO^zJC#X561B1!UASHkbpdVVIFLEXseD$dj-A@FOKs~TGccgN0fG|C1 zV?&y72I4Z*_+m0RVs_+z5vIWw?s+8i%}PNI6R)*R(ROT7J-@)#o{^+Wsz_EKTLcZG zsjI#j@ZH9_s>cwF{h3Z>VlW4`Ytk&dG4^ZLirmL*5c!E;n6+`<;uHl<0+QjU+| zYkfWAW{3&87Rf(=t>;Ls(`X=(-?e1kO?_yQM+8zyCQr?_Wd<*PeXR82Xq?c$G_3IMq*PXqC;a@WP0wawJldM1|~u$}~xDd6`dx>sFqr&;QH zaGvo1g@;T5xK=KqdmfJ;`WEIWxQ;A*i}+H5OGd8BvwIyZaNS3uFWV$vw3N#gUHIHG^ESn%eVizM~o(#5fP`Kf%J1DX{|hwB>QJ?nL`|?_Rpv^E$#()fCwrX4L zTH-s%Ocygo@eo;(bG(mCRw^zNwKsoLWqC!pD|R|x4fu9n4oT%--bePy4%LZyQS{Aq z7k5i%5lN9(kCo#+KMLiqFW_j>?s*Hg(X>c@a&caN;yrg&xtXH4mC`ZM6yRgEWT?*( zQ&3WkD03~2i^M)Qx3yU^#-m zKMr2q12yQD*4clE#z{Z!t!XNdO6`#tROn|~UR*<#hF3;BVNNqw1*WF%OolWK$zy~3 zxvdL-_@}RY= zM)$-20AYzpy0?!FE)?Vs?%-EFrRh@Nm$+D@mB%0fpTzoBt+t%L91SenCcd8u94Nqr zBwxfb8UHot1r7|DMsU`H6jkE_mR1YGMcH|T9irBaC_Ng;AEfsr~$g5xQh}_AOCKuU3&1&n@ zj_l|5H6^h(gft;}bN!<-?ur)q7uBl2i`y5xkkWS}itDuHn@!V2yig+JpiG~{)+O$l z7OQk+x_yW8t|eOaoaM4Q>EY!X4k>ERDBa4#1m_(yNRu+QBP+E><6Ub;R2A5pM@;iiNPNM7xg+wfNn#^=q;ZC z0zI_2;+>52q{poT8-eI)j4D%2BPN&;h3BO%Da3I~X^4+*IsIudntna07*hZPAo0kh z1ckxsXvPN|=8%F9BNPA=jyuyyJm;k@DY)i<3}>%OLU26|KLm9Yhw04%5P^UNCNODw za%nP8N&rJl$3FD)>^YzhQA`G&N4+y2rARwxrAEV&d(Z-Y9VqKSrNsa@prf@rZfOtF zs0NSDl?NlW7^AHKD`Tf>X+81BsiU0nP47U23Q7EF?57;kWPwOqjftFg~>|0UU}#20+Cz3<w z{c%8`L|FCWf(KDj?aef=AK^d@r06N(JJWWY6HG{N1M5cCr6BRmF4O5i#*jx`QUg!e zeT_Er2HrDQ*BX zr5JSw@}NTq{3&{IP7#wxP>$U_C;<=FnrYN+&q2)qGc*BD@|b@Krz~f`6ag6+=9<+h zUcA$Gl0cvc`qE&Y)i%ry)B%rb0>nE{r6~j+Dfk}rbKj*f8MKi~1_w0iFnlO6L?n1~y=DtpsP;(-hUC(?pD98-ot=}WhBbQA#*6kzA2NKvvz zFlo3WwE#ETfGKn1En}G9Vo!f05kehW1NvsDt#$&+JGZH@kmBb9co>pwM!+w*aHVrI+4uUVjft7!WuZ z%>YM?8g_6gKD69pp4C7&5k`5%7|kzE)PQhPO>AeaJ9i$Z-kYA(0F-p6V>E5zfE8&+ z0DoF;4k_RqiU3>;bjQ6jsRpFS6w)~q00F=vm$fh9MlxstDR6uArv>!wP2~3ZO#={~ zeW|S3;0j%|yACK4BcmK=nsHiOeiQ(TDntiqsMv*Ekamx3)fx3;8j)>pypF8L0;RNE zS5on1P)jfa-l)y2>F|87aHNCLMrx1RrM-zo#EOfaQZ*RQv8)1@UO z^cItKo zbsf&!SlC^lxznC7&HNeb_;xjua1cqg*>`ltd)Gb7AC&%8X=f4J<%u!|;C~HEhfdd&;QAEKBC)AEAnYt5P{_sfA=ce!uKOQNUCJn&DaMCduZOON3^d7ZHjQ<&~KKcyk7G62}EYbn=hhC(^z8nQ7{#N-ZeeW-D|8kZ}% zr8>Wz&Rf$I^b$w=#5-}pIN()ywJ$BRGzG^@gN#&=>b7cF%Vi{)IU~-;^r+=#oa=I8 z(fyF11hH&`+NzYn$4VsCA$IvKHVp85)+Byo6(X0EWmKO306|LJrE@2+&8MKG!Ojg# zRhJ;1Pikh|434y1L}!znR+fik_gacD&zBJl$N5!DYiVxhNMT&N9z8MZU4(JLqUmmt zydHQ1hku#RuJMmNqQUX6kC3g#~ET}yxz?&WeuaqCcB-p_Ru z_cAL?^v*!6 zK+F#Ytc%8A1A1eDS~_~@WUA?@Bb8&77_P5IyHjSYWpGCr70HD--P9VrVSDyQU{n-i z)Si^&u7ww7b*A;fJ6c2k000!_xM%+Wma+rxqN=Cw2IJfUD%>{yY2~)4>U-8#vARVk z-eoPhng=Lq{ubCRLRWF~~^`?^+kFQD^p@+zJ;A5raGUBvQS&hpB|nVTmj zqn7fyQI{-y!Z2o)BTF zmI2_dSDv-!<`QYe9hfC|CeBmE*XA2^wTSH!68``x8+M+<6~aFx;8#K7)nvbkNjUQO z$gX&`bz!2FG|`?aUj4fWxv2V+Qld#FLMcB=nk_y?Y&U+uX1Y0&TAbXi>{r{^43kqW ztL@7Wqp+=rEEX$fof9EW3%`HDs9fAeu68fxbIn4j&7w5+e5&lF(3)KFQI{;l_cWmT z)`nVi96EHVBbk?#BsbTsL`lcBN4V}eplzL2hp!7ajV^(BRk%K$wb$J1Hu74TZ0C*k zoU{7A|m@#bCv5=oIK16N?4ACsZqmwIdfdnt|3l5sTLtCA-J%D=X*+?(BuMmd_I+jk^?p!k#I~M)4;Olbxhq9vHNlmw|4?eb~-<{A&+X((Sa} z&Gy{OjD>Xj-$E;>mrxcq@wDR-W90*oJ#$=@^fuaq&pel8f^u9Blzx@TMs*y$=b_m; zapkf)tp~>THXa%U;PR(av4Z@=KDetoM~a2??;dWaSILUw%h}KONUkSN)HGcwyv4O^ zxkpBoGNAf_oL0u2tX$pC_LwdqBRDqX0k`t$MOrIaSc-GBR8FzA%UcOTUELs=xjt_g zIGtH`gQ)FeH`|YywRWak@Vg> z@lj6?4qjy~O)6BH(%K%2b*^c8ZlC0>?&VG+i+MG{>iWxSG6=-n6Z^fD1;&#OrL0_i zqV26R*+gNzv5tTWbbb+%+Fvv690CkdNtDmwn%0#%H2vo-Oyuf6b}=Bbv%Ix+yLWh| z40Z6(0+p=?o+G`ugtH8C;L_XpZo(Nn&n2YwK44O( z-jzwyoA=RCrzmV@YWl6trw7^X;{pXdJd2#x6~JdcTH5)NFH)rA@~*=F07(mA;ccQ| ztOuBkax?2#ky%~aUoEVGr3CIrk2nL+)|6^RM$r{WN$z1wW2uX8GFTcYN#vdfN zkSoYG=cop~I^yaT2G;4D%iDp3$^A22wdaNIE?4aq{#t|l50xbTBDbT8=3e@-gchCeqP5_$^Sn?lr}wt3k^S>tG9xxXr#Pe{B!Xt1D|NXK(F z&_;hc&`n6)GVYuf_1X!p3YDdM!Dx5VrKi5kJ8uMDUVi#X{Fui)MoFvD_-9F2+BBK& z<_pQgF4Oq|`qbKtch_J?Z*XA;u4L=@ReOCG$UtuIW|{v0yAzN<6PjMyZC%R6dk86M z5t>E5ooRN`6MGwwQF5V`zM}@O-s$$9baF=tX^F`@Nh|tQ8MIZrkVx0lqOU|&I3J0t zkhRUKh+!gF`e!FUokZn#boDL}v$KwbcM?Hya?wo*bd7#Zio~(^98@VSmrLyyK>q;p z&On8L{{VpVT9Dk#jC|->6!!!V)~tP^6;kEL&>m_%qgq(g*{9UVxiMNovL%dA#JrYh zLm$hvV~u9z?{j4$xo!z~hXdQvwkEr|x|NYF2F(59+q3vo_O^yF9kR{+zyQT-8K|#$ z8Ojam7FV@hT6A=DMTy8Ix3d16)S9*Vw6?^K?us#ppKt3|+2)mTDF}o3ROk6tRQ8JZ zTaqXn-dC)nB`IoOEu*IAFfJ{x?n5=5%#3&d$X>e3T{^+xQ*{%Z4kD4sBlW8y7mhH? z0uVbX3J>E&w9#+F+AL?(4F0vHO3rKDRx^bgJGXLcXd+ZG+ghw?o{B2lSuE;4(4mxe zKgyW;lu@b3ATRqksIB#@YnH;!fb`_^T#Zfp$4|P8X=QB)#Br)0_C-T=VGREOGaxPp z9e<@x_bIdlKaEB%LjM4HbNpVv;ZU50k~5yiPyj8h;Eg+@u7CRVqib(0OZ&zS{rx{G zPd#H9CnTS)YIU6n1fDa~6y-TIE>(R9r?vB!=3*B-sNhslY4+&Z*bmfZt+dIUbO+Xj zDn@q<3Z8jvQ&#}1jjIU2A3;=_$z+>%L7t+nG{dGq9+dLP6mm05o=7dwh{6K!zzT%7 zmz4zV_03GjKDAnG1tXI1L`@y$PAgWH-VSe#`z@4QoX_b(U&zVTEhw~}+cc9ZD1sNuKr zWhcvG$C5A)^`-0(dvj1J>KO?3aY0rG8B_lN0tTwByE5m>S6t@+07v-KWQ!_GBt>za zP7muxutDvMSS0@d59dHa#DG^le()7hNRe>ZP&#w8{$itOqeG7|7DL^EApRzgV!Mmm zmq=qr|ZZ4^eZYts(Qq8$I4F>{KR$!F0YObG7siy`vCUln2tw3C|*C>6wX+Y zl_YlDDxiRs1PKg_)G)v_k<62ZsG`$O6U0E zIs7@KxVu4v8lgS#gi-7edvm0C-vE+;f4;xtQJi%1V$Y#*{x!@EU66p*jLX~t27Z-o zH7z-!4AGQ+hkEt@02&n(9@yVln2xFVIHSpPox4st{KByn?dBxIe;S-*DI2l*^rwiX z@=GPkqk4_QKd(wXf)8$Lx)ofrMhClrOdA`du%G*AwN4Z}7juai{{R+28T8X?IO|`81cR@`}n`(SdumHn`~<+2coc1PVz59g=ko? z=j0>*0M$>}hqpEa51a;%Kkte~g#Q43FZ=kv<5^*^5q6t-5>*^CC^!{0t6a};7Dow@ z{n9_BKVpO1mwbj$325+t>7xGtjVOekl12Xje;52}2_P$yni^MjUGcB4e+1)XthpAHaQP0V0G#j`c!QRlwe7>(b(jE zf{$c}?e1UuGaf_5kNWDr<4g8ALgy)uUF-femXS*gc`Y1+JbZ*4{c1;Gb^|JqTb-$p zX!b}wvo8IeiNd^Xj-)u^n$WPuUM_uIf5xauDTVVFmg?Pe85I8jKGHyq6Dg2z$_Gk4 zk{7mR-zb1G@ZkRd(?$OP8c!?b-R4J+X$4j=pmtPO+;GF@82(g?YVPWyGDzsi#~+Ur z{fZB6%OzECOpy$@Kg0iETLxq7hrh}k&nkbRP6=e`SBZ*(4hYS>zvW-kb7eLKt~2> zdH(>Wf|KmW0le7ppS`?ds3g*(nB@qhjIQCw>rfk^oJ_Hy9ZKM4k7R}Ijb^uQrdYr4 z;H4I>MnsY0{qb4sz_1aPB#x&5eswm&?jep@xoqt&G5K*%*$1{Q3s>OoPPqHRk)iU~ zhLS&~@l~4;F-Ey$9SWX5tyh9uS)G|DX)*j;zyg=D4{Xf=0LJX5Ki(7&7m`tP+>u%D zE!$1Sy34qanNx62t8q-8-f~MxFP6mchCm19PuU^+W@-6K$09@{{ozey$=X?e@1a@I z%B^l$hTW$GHUKoKJW{DEAegDn%$$t+WM+?Kh3%Pp$!6h7XZ_(!DBH2Qayw?JwW}iV z;DU0h@5uZyP7yc;Mv$QHHu3q<>`;4lTWDN$3O=ohX^pUcX#W83p;@;+S|O9nl2AHq zdV*P-U@<+yGMtU15&Wt96fbVgWQ~dY$T8b+r750;KkurgZ!GcvM9Z9UxD5VPE2YMM zM2=JHgn~aRJ&^smIEbI(U;F4%n2NAc$&=6D6 zq$e3e>{_1jm2O%*`fe3V&zVL_7X)-e*Yl)`e<^-zecN(=c=~=Kk7gd&yz#Pq=Zq86 zE-AiQ10~}b@4QtfzgI%SFb)6*VZfvpGl@Bd83*@+WRJp^vq9~dq2&{j9y9*{0~9n& z2i`&d00I<(SiG#=TG5HTH8{{X;+EO9cncE;Yt zg)p2F9x?&!&ba+KsFW;+DgfhxKJgW9%OaUsf`fivBQ6vc&{9lH*H+Z7pjW5|-` zCKwziKg*h8-CBc?>N)BM_|eFN+Oks~(~O}%?+z&dbUTSb{{R9M&oVjjwaEVfYl?UX zrJ3f8;A17Zr^tiavR5kLNJ?%a95|?omOPZ_(Sf8Fkr?(z7n`07Ag2b}gdMA%fP!&H zvqJWxO}maTxIgU*)UmO#Hs*V~rAbx55obGwD=tZyMnp{D4oDnP6saPsocU)tTpSE3lY{>N*Q}<3L7bJqJe|U&S(V%=G6m=g#f*M5&FUQGpO5dR z-2Nh;vqDv!h`gdt44?PVr}-_$N`K!%otuJFF#xBz+x=KNEmWA0!3;a76*w~To6xHIT@#Tit;c_ z4eFow(5D#YmjIQ=PFP~3pGGI z$NR#hX|@8a4gu#fbNr|hGo85`3F8Z&%QRUbdqzer+#U*l-$I7Bl<|Sb`$DAhra+}s zI<$@EY#y=`On0rxE0c5eK( z2jXdgXO)g7L?8Fjr0IgGOcP~tj&&X+6WKqB_pk}5h z!{#C;J;pPS%9kQ5+Ib8xHtYsMeV7_gC4!Drlh`*j>7|ik5(F4O#6}0}RR!8B<+4wp zJbsjNA$vwxF-f;PW9v>-ke~p3zV!8-<&TBjx&9VE;X*Kc0~zDrH2Dy7vA<@J@r57o zAxf5xN@7ScB$FI;4rKFZ+W zh5QJt$@Pn=P^2m%8R3A-WBOHiWtn1mCAgEG!SYApSk6i&(1VnU_t%$k+)Vy{+hBHp z20#6DEub;^;zG-uvFH!eHC9=L+~_52DxL}BKZR)Ct3zmx2$D7%b{6OBSV~%}oiRyX z##%zxSDsiK2*w#kcq287dFMefM}5kqfstEc&N$FXxRM9XROF9J#nf`s9KP|`@aj?Sed%M3<&Z}hEX~FRBB^!?pps4i1Cv`ezhknoKWK_ywzQu(7*G?_pL*zS z$0SwC1yc|gVOH%!xy&pku;8uR1y3tMr#`P<m1@MX-aN5^Ly2Qwa;gDz8ML1{v*vKiW?`5!;E5}JDB|0&1<>N+fPEw zm#YBTKqkL4u~aBz7?5rxG4-lWhBqpIN`B1ejN+bwWr5X??+|5sWK&5kiEYbr1*OF* zsX62ittlXMAk~sOH`;O9tpzw4<2~w&I19k$t=btSR%a(&BPxGE_< zWO@|&SHe`(H3q)A5J$BYh5@m9hiuAxIGW8dhdnx$Zz~UsOSa6DpjyL`eMA0TgX9er*ExxehRo) zVZ3IM1`7PG^sYR0H9B|dj*KjvY9#h$FB%p8(2g*2%DF#{bI@A6<%kDj{pJ<5c9Ls) z)bmM|T;Ku5K=iF?^x+1c5+a3h#A?HKedvtUI7TMZhqbENiv_YQVL<8kfvw#x*(AMJ zh?w05OH0b81MkXTA24VTA2IIJqoyV%~G60$kJ8B20*5;s0r z3IPf>=j)7Sxlsg+rIZtnO?n2c0&5eOl0c)P{JF(eZ63|y5j4_m0~r7c){SKdD6=}$ zT}g8zh1x;s%@`uPX#6{+C*BiD^?ZLJR^!q%RN&c0PCtl}f1PUaQ88FaGtYrE;Q@1y zbL<6rGFa(o*;`m}KiM2q<{M(btSF>(Dx#s|7erdYzUQ4qXETCQ30N;u6x4}larcrY zBh=TU?HjTO7{^Rf2%}JN0Qvz?@sF`<2A5Nahep(9$XExi8P$0FO>1d-A-CN%+N(c6 zKJt&sx&lTv?iAJwhQ>fWduFnhCQ?>3jv5P5R_aq=ZN{X_1~4JqS$`_#rtswQT$!f2 ziB~)XQbrAL%DIdzY$IX(TXXtTqdr_jAzw4D;<(S{S;lS}qghf@T&%%g4%&IDsc^x_ z$peu80FNBjj5ZeNpJhol0nu0h!K%_ORF_l?PIDO`(yg)w^CMP{LNH_pB>r^WSxa_G zqZO(}U+i=sKxLoT%nN`&8q?GDDfH`fvbT-pR6I!O{LL_=ikJ*t{{XzgogVPK?a%ww zl%>q9%{`oVFMn=ac}|GKaO@QB{dlaY6x~}$l8GZS54=<{=f4#+O|nL3+JoDgYsnK3 ztoyOhD5+lV$fTE3o*ocSKX=;L4CPKRe>&WjX0%Ar?o{?KCoI&-*^GN_e{z54p*j64 zD(_o|Pf#bj`$V7JMHu`|ZF$wv>Sp6kp0=^Kc{B#we3GiJ-}Rw^Kbfv#ZxmSD#Lz;o zAstw#ApRUzHK}VCI+TT_d`1t;8t|$;g=elw&34kla;wPUtBlsrHZFD9=6%tsp!D+8 z3bCeZP{Vc}_T7$p7+YXJQCN#804pdcPdi67APDXPfywXAddS+~rksp!^7 zlET-JoUs{ei)M7tyoxZIOxfJWy4fZY92zQD>ZtX9M1* zS;CH7vGf9?l%-%0$Q+Rn!R|BuH6vjc0f29MiLMFzp}!G|X6&4>2AzXJ(OW5(&0wtg z>ItbOmQ^1o1O4Gl5`Z`Ynm}{&5^?QH&!}uv%xxfPt;3&Q8mz341;lX(&-D)SNaD4fq4_6f5mH8%ukP)kbyOl30xO#ZkM} z1-B8Zvjg`_WBOGpZbb9N8y5%dCm;|xU+#?;@qew1bKkd zcP72n58tTI zOz~2EsSZz=G3;|mQ)%AA=Zn(i$XTNV2WDg4jaXZmV_%)reFrs|Zr7snKD8OTC!hk8 zr+2W_(WU1rzT)zqU~nn_0BL0$Gw0N`WVO_UbTuEHAwT0!*$1?-s&3`nK6d=-QK?J@ zH>meK)(e+E^3ac^Ln0Q)^N;6M>>~Fe)oWvU^=*xbZX*Ey0JVw}R)SaBk}EOojMp@X@xE-oG&sDe&7!w_ky38C#Mjl8AGGj~M{KU%LXl#*`~ zT^)y$@?ie}g;qMdTga&RR&-2Y(wP;v2z!x1xa!=rD;8tYbnd7$K@T$11Wqpd~H$}!H?tGw4 zHsaFc0)EuUoq=GZ^2KXzyLH@SR`lF4RHlw{cu2zmz;TgA;%01GYdWz>)?M9qI}9JF zHC}Yopd~!SRZ-ZxarlqxS}z^!yh-E`2SEF2k7=ANvk)=&M48XzGfu!{Om%pd%vecW zec}Ko=mE_T>eoUtCXQU5+z;e4TCW|dgBdpy9=k|zqw%O@vayj+m`Y>SiT?l!EP~~K zwYoyG%62R810ZETfElT6?P9rN%?w0!-VQ%QPPC6t61oV>k;wC8Bxm!fUVGUIjtgrr zKf1C8C;Tahid{N5nTL^R7q6JDmi)0zjx@&DuB{on22uS*Y1-M$2k#&%SaHZB`c#KZ zwrs~VNbT9198w;pQbBZFgEpK>{q4B_01A}aMJ!%i?Glgn90F?T(Cy<=W3`JXsoDr1 ztsv2DTxG27+0XGPIQ=?NptUlj)2;2CG%>Vz?y}_5<((iohEmOrL%;ae%ob5@`J#w0 z^>TkgYRstuCQN&X?5m&YMuXPJGY!?tF*u4v>YTBbYhYPJY}15{)38Bd2J+YtnhKzzJE$ChpCNk_LjH;SBd*!<8Sdf ztDbcB(YKXnZ6O_zMn9b*q=HqrwSw4>;xW&Y`f@5kCBrY8)q6PxZFyGWCu7ISh70t~7ecc!@7i~Zn>I#2-i)yyMioZhS%Ux|M`8R} z=k>*GOKEZ#?~sh~l`20J5S_lx=%f% zzD(BJw|5hD{Oa+szA`H*l~i;Rkily7o1@qywOLyqe`!CF#TN@1iKyLqpc_UIblgV# zepNF}(e;q29H8>mcYZm|HgfDp7FP?O`Q;Jk@dBwGB+LnWNmnPU#&i18xLAAMTuD4= zY$tRb3c9vG!mm4oi9^G62^{>$jrvu4Ee=>@Q*8{RCntgl{P?YtZ+Ex`jp7*YgPJZD zF=b1ImMMPJL~eLTOnx{GN7x~`kUsd`f8S;OGHY^M2>?}&EQ8Kl*YK*-+T5dOV+L+G z10StBqHJJX+u2%lec2qSkUl=R4kn#dWkl*ksE{Dp@(+`y4TrTGS0G_FX_!@-Rvnko;4u0zlen3{uu9V>%c~e`a zau(4IxWau{+Hq5gw3v1zx6n2}Xfo(}8}$cpMj z33LwVWLE4}OdqX9C5kE(az;4Aoy5`1gO!}D(Op{gj^0?i6q92Q$cnROw%0_Y%e3Qw z(kcCVira%kwVi`V>IQ!6CpBrjDP-Us+ur2V zM=lDF;lShauBZMH>sWqRm53dhUJX;YvYk#Mu(y#x>K`Zd6mthD$}0Ix?HIg`l>N}9 zLHEvit4TGxx0=Ap=YU87hvG$a5y^KO1zTvB+FN{q_^yAIUsx_C8`&V=+kubd4L)JC za&0PGw)jdCMoM6ieU2(iZ9;2?Wtn1&94PXO56-$-Z0=b`VTAtxx>p~lrzNh{D%P

;nFy6uE&Tn3M?QK4r7CZ~p)-BVyS8qNjyCwK7}Ej9B0q7~p+L2Dckal2UPO=();~ zIsX6}V(GERyK~5gpv!I_tsKI!p0>{`F_Al9vPwVC=~ZR0j?_Pvl0*sMqZLv3ka?|! zwm=vy(&YN(Kae#WLpvR*EK+hYg2Vp+Wuuu3l*fpO$XA6Jf6qeW!JT@Ha>S41De0jwA%jJqMI4XF=8j}nD=3hxrZ$n0S+WGGN91XS*u>jh zB!jAkYzo;;Ag&maJ+Nsd(v^WMWFy#+DRTvkh~c>*h=w7a2h6R3_;XQt!9Y|*GBL){ z8#nUlT9H~T4}}haz0V(|Sd(q4hI@hocENAfn_}fK+TP`qWJ(GC5yAX(+Mt%&RT2jm zVSA7PADwN+wqabe#B6&1_%EDUX{j?fZA!8kPND%edQxRr!?qv z`($w&yzB_%JVzhPirPsdM&8k)XM?nmf0bWD41fq);qo~Nmj1j_xXcBzC^pBp9kZX7 zksw&{FPcjM%C8{S`Lkwk^%ucUeZYT8m2G1He&$pI_?wb{DlQzSIARBL0N^)w!2|QB zc`Yh3V% z9OvamP(iM;J6Ou^woR+US-Q@y11Dfi- z!g05>3I zEI-}JG=j*cb1a$kBQ%{xK0}WhW#D0pcN=7_BI*Ad%P$aT{YK zs{u~_&R2}exMMLf`OT4%$sr&4ADn)O@pj;RiI0W?#$RC{!QA1FZIR?{y-UMkGSs$juRIW6O=vktj zgJb*an)JOtM_45e5b@No{#C0zj55WtMGOzMW$i4@J)d*NZS3wM%uThB_6^tYHGM6l zfbId51px0%gZWpZu7hI~o4kxks|Dk6}(c62MyEZBAB{Faye0ND*?t$TBwP1S-{5v0z(&# zheA}3*QH2o!3DuA$D;-S*Lrk#BXA9zm>=BcrH4ecc`+Ti+nE zpI+5=JuJqIyTh;ht6r9#9k);kjAzhQhtq8mExuB*BhsbUEm1s$^m~~k#AsXCf&D5q z(r+Rq1TCM!y=EO6)q^lbjsE})Qoe(1$M}PN1ut$M!16gX>)rYLG(>~`2bd4bv2Zc&de@*Wj1jSQFOoWS2lJ}ZK^s5ZU8kwQ%_TNB1bNg} z?5t6&qIkC-nTgFhT{bJ0%4yN49ggMC)Qa>7?Jc30ND7>Gk#YrCMzFVV@&#f#Y)r4^ zT6<2!do#~s(Cy=kd)Q4LIy6jA>5hLIh7B^wrz-?EVT&E3%Nb+&*P|w(X}$I@;79bQ zG}gi+BtavSy9KHCvKplmi%l}#)>Su}ZRCrLm2v?61w}rSr(E3mi(@!dZgu+ZC_T@5<^n@9D*3}{^*n?1 ztlSix&6K%Sx`n;1y~g{AWPG9I9OPE)&|8~S+azW%H!kku(>0$3y4~sWVHG^HjvJO8 zy7j0;k|n9Lm@#wyt$^+6N>`K7jvnr|G-QU^bir`|W1p4qI3JC3+MS}s1Z^9K<8zMc zPfERMcGivMm>I(Yt`8M&Ql8G|PE?LGK+-5+y+_xIsPjTvkt$P^TCk)v2gy(A&iv)|Ud}+4?z6gg0B!$q4gVlV}cl1qa%=`@5+w=V@d- zXXP2>~lf^bepab1pqsL4D=AnWIq`=y3Y zrZZe<6uY*Xnv~R)ilidQ(O2R{LG@|;zaL;B>sZ2thIev z?F1=#azsb>U^&SAYgW%z@gyvv1=Y#}lCK$T{v=YfS|YBWyv>yOdlNCnsT!f^JhDjs zRikh4;?o0uqa%O07$46yt7UyO@J8m)MC1&sgOT`Fj8f_ikRju-1N><|Vy_lv$KVYy zMhv$s2T{Ba=~kogMu?oXyFdH#6Ix87}I6dbl#C^a<{&~KgOxeaI6~iB2 zHx(02rjvxUf2&S^rBHZWlm3ENz6T%HkVh)=wq)_#)^0QCY^4sRqSlN~*tY)w+eSau zoQ(qXj32H|Rv&A}%V@*sC@p+H<%18Y0aDI+|!WO=uz{#Z##zVzUE4fv5q)v9(6Zw}_E zxP^E!0BOQV+ooIbtev;a+50KyzXqIFQs^ySkE+kGzOhYpH4KOb2*=|`+7&th`Bq7b zQyiOT6bQ8}ijJ#}4OC;;Rhv6Uo!90Hr1z?lR@BWlwlrqb{?Tf0qBiRoZH6^}n0_6ql!$Fx zmP75D(VtD4!rA2;mNs5lfdu{)goyIFRRx=_1!oO<-5XT*Qqs(l;(tD1-!TASWahUt zuM$gdvfkt8h;SH_xK}>vRPqQul)!LuI{MYh5t_3zRV4{?EzeGd?it|{MIy2un|Q@Y zB##{PTppFH8&l=6`B?MmU1V0*(x7o4%0^YQkVSd(!pS`m>C?tSOWxek6GSP(=>ZtP zsy|@VvH*l1#*ggXRP=-OsrszD8hvIR%k6SA$rz^np)hkMYM9b>Gu04%O+#^Kb88uv z=2)Xa+?>#`k{8yX%@K1LImsU8k?&wd%Mdxnb6otFYZ?YFJ%&2_{VNjdR&X$k8S~fD zwT=b7WMe#ZR*^?ds`!>01S<$voc{n1W%|`y9~i|T*?5LF^-+;rVI`Q7Nhgv`C3JmK zm91l$<7&5b&rMtSu^P(960)mf+3;`T0^#_rrxP3{lg zCZHNc(+$l!EC+4nt=Tl^2 zw&k)<-amN#Dx%unT^-D%4(%bw^Q|L=kJ>VxG3^tkO-&{nkd40lQ;q&kcLDS@i){zl zA}m{b9_OI^D!lho1G!uD6#lHQpe$UisyF`I>T^pR`|?3(2STbhjnqo7oT4wa0a6ZPivWz;E!ZKebGQlg@L^c3MA#V_+LokYn$8 zNZ-!4MTVDYkF#0Zg1s1~bjImCC16Ooy zU2QDxt!|cAf_V0TxHxXdkzIx5jh+4k&=<#GxF4k?T4kIm`>PmZ{{U&1C-td#nr&Ic zzpB+zPnz~Q-3v#tz0+^zd-sm!Fidc51UUSErE}}{$cqTXyH6i<^InA`+QI;mcu{=^ zAIMZcYtv8?Zc&G)CX_J}O}kuj!mdtM^*A_Uzemhb&%4=AHM3@Qhr&r`D|#_pS41^E zHUpJy+3$*0)~$v%f&uAOD)M_kP{SxY^Ofy1o2l1*!w5&98LJ=I$RHrh#!`53%H!!m z>qy&xL-`IW!rx0G{G%L>E1Axxq16~c^(rQY*ydMnpvUv6S3*qu+mF@p{Hp3paLJOd z{Hj6-j5Le~u{=~dv+iHAfl@sZ+B3MshaK3KtI=7-77D5Z^a7&>HEfBA$5repT18-a z&!U=#IX&4*F-5>Y81uhcD?jEPBOjTfqTXd4o2*dBwXYdS^vyz&+DZ2=M5J};{xw)d ze{`ixw#>fYF#NMYu@DW@!Dh@y-bdJL-Ldn<&nDN*%Ev5Qy0}~e_-3ATyGA}>A~Wg; z=AeqkcZ+?T;WO1){J~SlX?jN4BaeUgqn8={4Hg+z<~s=&9$+}&k&*gTcJ`WZb$IXP zQhF}%Kb>8ZL*D*mpLh@NWAzmu*kK1`j}s3`6ra}=SW80FTSYR35-}aHK|ji)YgAR? z8JE$e$MO{#X|O`u;YdmU0A^A@O0XuLHY)MU&qhE9ABg6ahq({Q5Xp^H0ryyvKQbw> z-9}3(+6n2fWK@#O;NT>31KFEt&*CZZkm0=Bc8ed}A*!q*wdIV{;o~nL$NaHvr}d~I zwz@5V*D3EJ^!#euS-g%~8||mjun+X7M$$yvUS?uPBsdHF=n+;ciDQ|R-ayii?-GRn z0F7LW`$9pyO%6K%M2_7Ln7_|8U|ZWrftDDzj)cjaepC$&BJqJ&Yj@9Q zi2ndOcaX{k6&Jo21N=CsUK@zd?wa0DX<4}XP;SyR#MYo5hs&3b)`5{+TwA2OW?5K~ z{0iU98f0@@+^I)|Z0sc?C-Tid&b5?*=9ruNuHe%i(eQkhKzle~Kb10d6?EHte3o%D zAMUFDU8$nR(HnB<(WCb&HlNV*P)BL1Te)SOB_G_8zx{P>DPk&%Kw@I?TJ1mIS7TB$NeJ9gCXfB%q<(x=m{}x1L~cEqbC1LtGy;s;mY}hP zTSZ_ymL#9$Pz_3BZqeI9wz40*LFAg;DnKBv&OY%K0kc_&60%6Ym-VAScRBH>YF~D2 zmK_;}e_D;NbtukbxsV>aa&cV<(xF~rd15}taQvyC*?=}lM_=y^%74O^gVf2k)^9>8 zGL6~$*xjGPw19;I?QQSRR`&t`m&@T%?M259`>F;&U!oS(v(4vBwgKv~KR@Bt(r z!0|?du<2UrPmsJaN9f2;;f&J7rr9{PWuqfHb{hj z6nPW{h7C5zINNHQ`(q%}TEPIoGDdoU82vHRIQ$JoE%oAMWwnGi{d6n{{5h@SRHjT^{{VChQ#Uq#OLC{z6mthI zhFzt=kpNplr1irbegxE}09%&4Rvp#&ABA2<+DYtmsZLwpOOCn8hpXZV<1>i z#-bVX+{VO@%A~iLD)5spJygCwB5_+r+Uw=VC656?%~+dDy@^XS4pa=Bfm6;`BRTiE zbGQeU$83ScQ<8g$$ClBs2d?aMTgw7&AuYBO)ZmVPDthVDNCskN-RyDvsq-OnwqSWP zMF}SANP+(AXY>^kz4~CK^o{<@gOmE#P}(HXxIt>A@z4zMQ$yj2?jJ2{7FWBRQszO) zBaN3<)nj!@LXq2Yu&Yo{soW~aL|Z*$anI%}r;|Z;CO5LqKe%bh;ejQ*az$&6*#%JJ zA6hx$hsh(HnQw^VL7aaQarug->8Ju3kjOvjnIgKQq)i-+8d}VD^#Oq&&Yx+fT+b(# zkh%`#h}%ax+)5I?j!=h}YR?4GsOLL8&*x6^=0p34Vc4gSlrQEhxSzt{z|sj4L&jHT zbNNM8xUaLz?nrMGCgl`>`NfhYRvu;?+ANO-j%xVTIX}3fS$%gJp{{ZV!u9WCW znt2F6#kiCCS41t2#bX~+>7UYrX)Mm1ILneBRb*i&VV1bj!P^Z62YN$GWP6%n5oCg;BfIr@!eO!*#^vAYQ z8@IUrwbEOrpCpH5#~n7*(no5nfh`?}``v22p&riXKOKqTMXGPdsU!K*j5i}OwX%CC zU-7PtTP(zdwJIC2d~KCy(Xd~xTIOo=nXv5i~olPC01Ll;0+%F%_tgfCi4(DU&-9Ic=^T1_M zwoIt#38@y!H$WJWM?G^%)M3bxirQ%)ZRb711CPR_x6>w$OyEkV_(?y{wR8d~Km~%5 z1~O)YtFSy8jQDNbk1}#k@eoM}@Nj0o4HyHP@^{6G)Q+E{K?La~3zUGZah|Zm6(wCJTi5v}`KPqErnrcY07%~9+ zhoPahn4l!d8=U_D5)9RgZ9?Ac?pYLt>K}qX3V!kp3lUgDAI^|R9#kp9=Ayc>^W!Pz z{F%v7flr@Uw}cNcw!{2NGf+oyYa{uw$XK3$0xCn)rrK(=lv`j^vu;1quUl!*!5CAw zceu`fI-cHpYmmzngCnTv%`$i)j03r{Jq~KFBFRA z*@^DLg&jqEBIwrUljITQk4$2(SxtE89z;^Un8BA-^Nt8Cz@{ntwj`cPYNX0ou z!549}-N(NSe7yDCDbU>7Ll+*IS~ zMbv#hVYQTFeE3bC)%uy zKJkW4`bhnPMhEq-7g*A~OQ>J8jiks|qAIY!$76%~RLwn+RNQxFdumrw>Z0oMU$w%H zaNIE6>?;dXo&MU)jG`uDR~-l8U6r1Ner;3F(zjd;kr^98u6vyG^rnx8`ktwFXFp)m z;XNaEB(MAg16e{Zn(W$gPBup&ZsbM3$`Xtb&jfqZpj5Xia&WF+zV=CV|* z?wPZynzKir&L)CFjVAU!`Qof;7V=#AjpVe^sUIq_8@cAa8rR`%gP@w`eMydXFaZ;v z$C|Hs;plujGcAM{B^gKpQW%r zxv0m3^-aZHB99~tmjn;Wm|2*s4r0%K)eh1QGlN|!_+IuEa%Q(6b}xhY0xGP!CE&>s zB%kguH9FkBRbz9)H*KgR*-0|1B9)3aUD*x613kt&S8ZXSS=s4t9;K$(%CEa?&Algc z+zxOE?Ob1hY;P=lOB9SEgn4HsY!F9aIpEi<+$-FwnPf6L_YK$STotHE&GNe)v@6;c zk>UFFgm$`=&|5=s6iWCJ%gY1(O+%-7T70JZ^4Q9;93mDAm-Xho&JZlwq=w$zSe{*w z79T=u#yn>w=Aq)t*Ow0=evu{yK7zVwxh2ZXD@HO-NS*a7O>0hnw_mlr$D9OF?LJzm zA2Yeea(Y)YI)Ni9uzVb3a1YbHS%*f{t{Fr&Hx5riBR|g-+oyuH4KquLZ6QhTE)y~^ z#$z4S=Yjg4aYUaZz3*{Fdqry;1kpx}6(!xrB&l3`d(#SW$LC$5d_LDEbLK~F7pan8 z@T-yhGP)x&>K5_;0Jx0b;aAOV3FL2MfFSLt*jK4V@WRxnGio;sf7pxx`sS}e@Xtew zK4H0(kMB#5LsiEpK0p)4;PoCl?{{Ra2G#1b<*vMRZCI{(D_K3zo za@h0~y`$_N#V)4_;C&kZ08AHce`xTmfNb1GbM&sNMN&@Gkk#g%F`t=^0LC#)`$P?l zqY>yanpbb3N(wzni!-MYujas2xUMhTg!0GWO-3FAz_$k-Ip(E{%pmN9f%ww-4&G$p zbx8;OeQowghxMgxKKfDf-&}nJn}3j@V!C6T6+Y(`I;5@hd7O^RlS`W21DDLC-X8N0 z&m@oQz1gW(!#1kf*t!1zcR$XrG*>Re1b}<7sZ!mB8d-@W(wdN(u|;$U6!94S1xUIy zK=7b`8Lco?amhbg42n8ttC^B#ojNeQgZL0CCeooNCNO{AV_Idw?fTLZKe|6W)8=H@ z%sOO1{{X3n>OrQqlvwalNBb%(Sm&k%E=f2bfk!h77_FoX^CO?gQ-9$fw{BT7ea&rp z{{UKdIpopI#bjd*5(MQ_wLkkRFZt#_8rC66?V32wH(>oJ=3&Z=(WOJw#GL;CvzlKbi^ zqZH|n1%l1H}E&7(AOU>{`Gl89wE6%mNL7@939xrSY3F!zuFgI#~TY4C+k9t zO1_$&gMF>R0vO?MHgnUC2iCa_O6bif0iN#djuc}deuKSY$EjRQpC-jxp-)D(pT8y<_{^ zn3w(4CX>WcUuYH{tJ)_mN-`J`#w)dp!4~Kj&b*P&qJTf0UW>w-L_D-oIrMot{#BF4 z(_1^5#{*ULv7M&)h7_@dZWWqA!6TAt@AmXk3`c7#f52-}-%r!6h9a|sf53D6>d=)F z?xqMG02!F-+(vz| zT+f{C$Oo@}DaKh;@>6Yc)x=JEEe@JEXu;l2<_h?HMK0)~Mb39I&mT&d=*-LvvmZbJ z&-1NgE1rWi(<5W%TzySf)@r_`eOjLOGlq?5pMQLvzV;|xD#Z>(nseQTYT1|(_w$cI zOlyTI2v){;%|o2uQq?#uko2>)j~i{{1osL*!mKLA80Y}1i*nrU2l1wfadC*6Y~~wHn&cndNJu&4;8c-#c$_?ok7D(| zArfs3Cv)_hkgpEshHiPo{HMmrk)nB%+ayPBJOs17H%M zkA7(kL6{i_9q>;z4y2y!(LxJkIyJO`4$E}o&~>Gf?og*>I0L3d1bmiL&swJ%P<%YpBD8OLBdKV2{93iQ^51g|K@LO+7=EcPcZZuG0Vj^(84`x5>mY zhdzTN`c;Oz1Tu&nSJZ)5t#!FAqw=CuEJytF!2GFu-4N7Qb258PK{;rx(MZl-OdsV- zYi`#iL&*5Z$dQ2{;Zjgp zez-K-T|<a>&ieT9a z4CGrBf7AEj7l-x8U$!Z0wcl0P1mVml_Vc%x%vuo0J-E?+*`tv0*1eajrl zf(Bkg&)_N?%j7v=BS}Gca0%(W*2MC_(V+V>M;rHM z^Fx?&7}5y5rV)jXP=4t{$KzC@o=CwCA@(~7t&6vYLP%9EI}&P|I7tg^gtt$e79ag; z(lXGAM5PqKPnQWGAH(-|ewD3g#deVu=efASC|)3i0~Zt%;6EO9fFG z;BW;@q&TZ;46Ah#mQ~2q+G8PoPBT^%#DFT}1Jabpz4}&nWtHNzgj+Ow6qC;& zW}s+bgfE!Ug&hVv)y>FG-&%rnP(euJap-8f8dfVClrcY%8Fy2R1>k;Fry8?cjo=0& z)1_xU)b{b6u&SW+6-D*80Og0^D6k;FW{ z6ds^gH$JsAR|FxzCxcQEE#oPXg8C879IP&xv2kv!V`$VI_2(b0XA23WoR)tr3Hzwo zppP#p9PU>f2H@tAB63STzzx`PYOK(-W%$0&0NNdnI_D=e>{-ty64_*L;yZd)2ib0< zk={$PgE$^uN%qZj8f0}P9 zI3)F|*LOCqs(#V2y^bkrFHr@IcqLi$`TjuFSq+?X?V45(k)8ej02-@(s5BP-S~-+| zcpNwX0A9J{b1bs9=;Y6CI5kQw?hT{0lI|GS@1Pxq?zK^`EM<(3KWk^9d>V>9F+Z{n z{{WidfGkHiK7z6((@arrlrbc9=7_;Ca%kzJOGvHAGAxSRzsnfp|A>2n(|$iSa|Dme&g0sBI#`U>XW`cd+!!jVRxy_ zQJ#3I_C}tGfw}Kgsi8WI)t2JcL?KzkgVa`Z-g6-gcY)EA z;}ta5fFZ#hF`A&#Fy$DG_Xe&oCCF-9vdSmG^G8m4{uQxr7!iq@<7hpXwPMF4g^!@_ zYG~XR#`YuFRk+-RD;)v3+aTD*fE$LJK1#mg!{b|O1yply`>6WH! zHN>i*l6Lxo(w1wW(5s{1^gXK!&m^Ci;*UG!#!f!8y_sC)`7Y&7Lld8Rfa+3?3H<7# zT>Q8P6;??c_sFYJA<3JkRoxIA8Y zS+)_l9M&On4p*OQRG6c8PtobhrHaBE? zW|3~=Cz3L1c@rNqMdPSS7aN-%#+&xWbB2+sv6dn_Eh@SZ^59i+A#<|No5xZ;#Y+sT zj2!Yk4N!BD!4)5x!-7pdL>$c0l1Ph^i;Vl!4IE|n5HnQX==3?L*5$n@q0zcm?@vH4h!p~XT>tZ)Gb{n}MSj%55gQOGAUK*)G5OMP=qB#w%4?MB1K(0J?5 zT5H1Y2;N7qr^+0{iHd&~KD5@lMjRDm-vm`Q(V~)I&SZ1f1e|_VaOoD_S_xs3<@8D# zuwC>ZE{&1KX}fX{JbTuZHg@q1iv`99p#%BUcN%4t%6U@S%)t82NAjbY4$SA~`#erS z5@dDSr2haawPOTQI+=XckEz|2{&lGimuqbwmj#l>xZ9BAoPB!Iix`;emg%PTR( zE1z$PPlN9;Xy@A@Z{xIHW}+OUI$+jO2A){?)K; zx?myt)RDxX5JG48a4K$!QD-{IJaTSSk%9b0NMN{-V8bxL>IoIm{K`Psq{0HtZ1j0rgUQ}&Or zl{MVw(?--HP_GeZ9l^)xn!27EzjO`$jt5`$+5IZ>ZgMvq;PK8Wk|-n}FoJ)&+){Is zE0H0;@Q++2x}#k|fO0-=l^^y^)NY_p5kKDAbNSV!m1D*VvG=5TOpH4ZY?_rR6=;Q? ziFN0BlHMX?x-T`Tx@EdVSIw48{orNWpUSel(4b@=$WtGC{m?%XP~#`N8fhAhrRj>{ z-5N9K(sic&p4>L*Y?4^|aHl`5WV|;L5A~5K>7BIIis&9MTW_MRFJ}9XopvX?1z&U6 z5CP!uFg-ffEV@#*w71qVPUMUjByh@j=L4v%8LXOaB)X9Pl-O({g|`Xh-Z{u4il-HA zL!IA48qdQvT4m9l^r=CS*qzsmy?TNN@-?a~JU<@UBhxI>P2E;6F#cw=SQ!ZqBUBC_Fi& z%*^*1U4tC`{{XXqN45`Iv;P1I7M6Hhq-H%fM8tjv6(Va^g%rmO(r)XBj~|^{OQ8<^ zOEx-wlcqWQ29<1*F$BtCQtHF_fyWi49I{5|$&I8|1U6TkpL(MPscOl*M>ual6d%r- z`i0{hK#`*V0Mls;euAN*lCm@*hHG|^MQIdXXaZ0bS9tgJs#jVVx)_ouLW7aJoPJcq zGrr>{qqLrj5CDITA&S#>_LbysS8`5&6N(dSVe;ITJwHsfhitQKk&flr7-9O=QEOuq zs$(Tj-9A)7YRkzJDH}^303?7aHE6>hkrOAoXZcX2Amvsel`S3FnmHvSp<)O0tn0lS zPrQB4Zd0CcpCXEhme7iqX7Et0{9<%()j7b+7^Safs($4-JKCzde= zWB9gBI{nT3izUP`ZygTMr}U`gnjOD1z?JSzRyS5*xw%Y__JO3L?{Lu~%A)OM%76z! z%|NRVa_WP3s8BQb(>~K|PSE+!Akc3uiw&*2JPcLYis+T@6*5NRz4_@-`NW}R3Pybm zJ~EO3LSsJT`c;7s2ZQaH-G!o8TPU-YM`AsyPqRfA%<8HL{v%c9xtdlbeg;zuKV|O1~nM&^T_Xdk0Ig`Yy zoZyq{$2DnYIO70&Ra~ee8T@ z)L2(783(34={(RsgYg{IU$#a$CNb^Fr+uujz##n+fVpT&Ng+_2dsBlkEx{i6s*5y- zg$zg26g0B~_h0Z72)VjJ>-kVajFP>*YKu)KPG9qJ$LmlVh%<+hKgLY|09pXkFSPv5 zqv}O8&ZDfcfIr$5ly0n-JEVkvx;KAHV``Qtyt(?UNBk++4R5qWbN-N@o@tFdL#@F4 z0X33!t1zbqX5;u%{{XL0wz+1E#9K_qAH+b<>rTLJ8@M_NT>T>_`cq8Nx$7}L%Zlgj zyhm+oZe8rzf4Hw8Is82;NpUR9L7BwHt(+*uOda-!%(qF`iVUj%m0IFm4 zYVvze8tZ9p)5&~|kKky*CYnAb>!6TQ&4+BjGe*4$vg&%dbsUHR{{Vqg3rmcoQLnRC znA`kL)XacfK?@I57{zvaKDjQl0=>2D0!(ugFU&s-(C)Qj(YqRQ-ApbL9lKeEcdC*< z6N-4ZxpoB1pyQ|%o=&1v?z;;8w#J}-w1U?3pPx{d9r3kC@~P*sr?c!-ztd#7wRr9> zTgxNn%K!(xd3L8{Qb(2`XSNJfPO3_uU~yis*3snox3cZ%L7a10vuV0w!eSRcD-)D5 zlE3%JsFhXJ)->IZC0{TGWL6+>NE{EXCzC9Y`KM~e4mmg{@atZbx^|gtPml&1?=I7| zhrL$2)2!tRVo1>c0J9hs4-o~_rH7m7dCtSob5bNSFf0(T9mi_!n?{O84$Po|{1Jgt z{{UxOsZz4aG1KQ9ew9C4hp9^kH{@}eTT2e145(RNHVhaHkViSD>XK@DuAtXfW_hKN zvK~zHUCdVY(6cf^70*}AIK^z>e&rZ1OpM~P`qWgptZcB7<>k5Ok?GRO^8{uCgR=yR z(zDZN3dtN!7<09O2l-cI5m5Y*#~;uEQhC_pdmlni6s3%BWHo|Ic5;Km_p>JNG$AAI z$PGakgs%57d7_QSKzbVNe8AaK$8WVXvW}xG?@{%-oeTP%HgjXa{&qb3%n&*vcly(h zg6%+D-b!2ezE!S_&7MXS4udq!=yJsL^rZ1MUs9G9k7F9!!kTPFSec}8>e&85uC|M& zy9RqScIY<~R{Yi6IN)ZbTuTbk$d&s3ilG-A}V`~DeX#y(x1r=&I<4`bw z4?qB_Pchs$&S}DS18Ksa*0X%isj5+1BKw#^@(B7PO^QU0A<&|41Rfj#P!ca+HEd6s#%Zzs({L-w>T|(oYv{(nExyJ4n zwmQ>U;@zEuk^a%9wofMq>BcBj&l`yRGewYGcbf7H2P!krU{Kq60}MO-)ew#{Iu<$W zoK%rUnaNf9b)v`!%Y*WcG3muU8I=g#fPE?9V*v1Xe|tQNMr2$S$v(7P7^9Ff2iKYk zgj31moNlCf5D*V1(33)At^)r6vq%tcbQw9x>IXEQWA!_-e+p1`2{;4x72I8#qRyyvLwDOEto0G#%q z?Lp5UjUXGfcXU(VlS#Z}jPdD883coIZ1yBm&IsXg?l3bz5&4Q0gD54s3PU4o!ma@9 zMJbg)#^LxLXd7I7yNT_^QUQcY+~k4jieN&dlEmY&sU2880*}s`6zqq-gPKKz07)n@-<;3`3q_t$Gwx`qwvoV5kF6A44E0-^rIayY z03NFvgH`;;3;TlyPrZfBIz1|D=^0^;8J&1%DiBqRFAU5s*=I{=WAC-IpQU;}(kSv2 z9nAZitAva%lJXx_QHo1j>!K7bw?FRl{OhTETN`PCAV}E29lxDix6^GdfO7%?p1@Q( zmo!kerbM&dMPR1lHZ9duVAdqF>N7|;VOO`O`TAF&Ph|_ov&#i>fDcN_e-A>;S*2z4 zRT=(tqcpb@i)R@7gA!gy!28=HJpMHzOCqpXokJdl*pJf{)Jbcm+ejmx85jMce)r`{ ztz!UowY7w|sV6mF*>ptvF1IC<-&Te_y}~%ec0g@@09UU+yHEcUPs# zqBM7TIsX7^x!E-fXs%1oZxqXp!=KiiYB$hQrjt2QrOqMEt6VWX@>?HToqRc~JRX19 z{K(H!U9PnY$k-e{?7b|g#dlt2jsJHBtmwKR=J=II&k5dyCpl=ZC{AV~;z zMLwIWK4*POwTzuM&N<@AyOo>%S^)UXYs3T*EMS%(V{z(#8e}UBM3Ab7zW`K%Xqm9G zDL&OKZN8;Rptxx_hd>XnrAo80ZK|)h4y15uY=Cmdii%69W9N6}MTdTYadvHC4JObB zVb-ces7HF_2=G*OJxxb>e;m8K*cD0)l_ZS|)`nLhWXW#842rJy zs1Pj3LXp9&sb#v7=XsBx_Ii<9ma_R#;!@7I`=^{%nxrir$3JwYGlY2w2aJvYtxZ1C zXl=xfwPL-PTQ zRz&)Q@+gyV+In-J(z0ofm0s+AvvO8^#zK!~tlziK2^)YTzcmcEqIq_13n2dhJt{kk zvXQDVUA_1jAN^|9DWV%M4PijxGT+@Mri)&cK~Wwnoo3ws052YuI=KJQH>m?;5j(GIVPBKn$f%T@Kx)aZG;@+|0hDWHYacU)F*rbL2 z?rT0c+?-(J)|H`=U*!b-DY&5KG-1?Va2{bE*{fe~VceR?iUvP3;Cl*~DIHsimjqWk zG=A95Ml+gly>05*G!CbF)TWL7g`L01vfiJF90QdFV%4jLD8V z(?%HQr8OG~%H(A^z$$Wk4rz~XC<}(jZrQ1xJdc!&k3&(2s6p$NhD2kB5{*|zXM3KURtGWpaCbGYPt z(^}*)%K&PU#g;4PQyNDgJP4k#B zIAE%HrqHiq!`iZz+T&s9DqC30Pq>7C_Ftt_I+L`4JgI-*D}hnRJi=1MF<((jm!V2l zFWjjzmQVus$3aX*EWC`4f~z~u+^^~>;pPB*r$1V18Vj7V*89|k01B$nY~fs=!ky<_ z;N=EC-lqW0GSO{5xy>sy4ctX?s!IXSr8+s4xgW}t?8J^sesrH?$ouRMwrZ?R$;HZ! zGn!Zjx**yX$84sjL3b>E>ZIli-*qxBifv1anDTD;@pAv$P~Y4{{U?MbaJ5P zD{pde$_Kt^2*l)`D#(Lr?0&SqSRI%0qR2Ul#~m^O`qJYAY_dcoz}|-S2@)_ZzuSdk^7b{{R6r zs?0&kFdn;V)=yB9ezfHb!-YSO6v?|NkV)Yf{uBV&&z650(J)NIK6Ge515OsKWI3Na zbO2-dQFMy4GG&FPA9Sug1yMHcw;wH(u0OsUWAd(y+b(hD1NAij0I>{x@PFOSE@+C< zF+8?)AGwhT^&r&gXmA)iepRk5;X(7r2i1*F_IMQomB>B7CX=X=C5Mny?IOp=E?MLjHlCXh{Dg3ES1FkvuHF+*znsE~3 z?&vlevTfrF{-KbLGDb5;Iu|1{JcY;J{c}%Sd<^nE^I8Jcyu*(r6n9~gYGVvTXnE9< zIuJnqRGmTlGb_sDhC%ge5*CrL8BTt+vG!k-7`(DE&oseqp#Jem{KYS7KV)Yc0zUMq z`kIeojIWu~`=HjlLuxQeV;u3CQ7x`8L1EvVQud5~%*Yn}XUl$nyifI~i&xH77x3#^ zB&i3>h~wR|ZqTC%3ua0Db1{>h=suqf-P z5?eZuLZHuZ#M6Shf<&1kw&PP0ReFHGS`_(`%K>b`N-wnMWuXk6pN=K3L?exjn!%-Q|x8KT}KQ06TuPhAE0X zZrJDCnm6Nsg!{HlF7R=|{YUemnk8YDjAOGk0bvWVI2Z%jDZX0gCviTU3JvY5{H9d; z0x1Rkml}EQQ&T@~`4g_*71e2@5KYIRF+krtw)%mK2X+ij3S&M-hzoJP+}w zs96q<2OOE?Bd;m|r^y-+0r`*Cs-LwtR$Pv`B+xCSVS@4T&kLXCXuomTvy@B{6anrj z1j^Xk`cwuG2v(Gw_D}^qquSUT^Xkn-&{^~)X>fAPs5=bqKai;Y(5g3Vb{?nYrjfNs z`A2?F>rjVAExJg^AKm8^xsYqyG z)pNxoDGJPPHZFPT$M{jqgO!!`Ir7t&kY|KA4-S&7U}RN)FhcfISx)MP^2g_ zB$kgrAv2!0h_)b5ESk&P}{__+i1@BNP27<{v4~v=sx}9#Y`qO0b7PlgRmfCs~>nK0goun(S=Dd0&3B>b2z;;N<$N1H#t!!Wb zDue$3Y_VLc_=8kQ7)h$>adgIVcKtC-n^ck2g~y1k5NA1#GtcH|w1b)_T`lFD3!Rc5 zKmY=v8nQ|YLuKhkz*T*#d^Nb|GcE zh?Ce3Kb$@AC6JJ-^A5+8M)`t`<~8~bYSFc^bBtWbPhj+Yn!#KtvwV)yN`J@v=V>~sD!#?>HR36F6d*kt}@lbF;mq9PMr{{Rh=FZ>8* zo#x&*@a;Z@Ww-3G3`c-A|CAb8yn zj0(U$D4Z_S6kkD;{b~pC71Im}0*^&npR6v0tSpX|wA*eK9X~3~;wpL?EHyp1G1tQ{Sj3S00CO%+Za4&&{5`|&IUbssrJ=7mGydEiZOU+Nlsa2kdIk0{DoY% zva_yfv*OHcu<)7a6t@{deY3;d*($Dt;w`J+4%f2A(Za*cHPgl9HY5s%r1ZcH+v83N+f2$=Ofmuh5%=$y)OLs$Dz$GP`PJzMhgs|dPFJ@ z-WkuQLZKpynvz&m#V_|mn9dy*ou=YnzA)6tkIEW_J(f2BauDjbd5 zhhiuamQ@P8{b;ZjP|7I*>DHBil#+QpeXuGrM&W>S z^`?dx&IuXx#Vg39XJG*MCW`?mY%d_=>J2y$M@;@4)peMR2+)K5;Z6?h{{S#ze@a#h zjw`r;ip2L?Qh^3{91hv*Q0^e(1pO(&hQY#*eDW#SE=cl{pb~!pnm}B2JRe>unP=(K z){q`PX3jkY77La)Hc!ow=}f?K+pv8NM$*U5&c2z=D3|?~rC_-#s-8wO`BOItz~}`b zako2qdYX*Mzve5S$WyZml6lDChQ=7~MgjcjEg8X50~&7)fI?^0k4i>-0HuliD6kGz z1F3wTruRs{^|9$K0@A!%o3UG^3yv8gR) zhBJthXg-3u<99h=%=!1Lg6)c_ECF9i&ykj-j)=H8HA?bk?6~?G&3pFa4cpeBlJ$>6 zNkxW*sjiY}SuP@01EU_*#@%W^XStC^ah?D;t+{TCee4lYLv3&78GO9A`&{GcS}JIs zXL7x-i0N<~_gDnzIjxNpj>fpG;z{&gjvQR1gP4S;pc$`*r{aLI+B<1WTyy zS(jFMzlUyn6N<^Ww=ytsGR6t#FT46ypo$YBv&M`%@@LgvP(3NC!%mF#4}pjuxR4x_gsvct8ry&(g{(dj1kJO-8CdaB>`f<^%?6)#l6br z>|dK!nrMJP&7VvPNM&_9r-TrC?loV8Adms}rb&7SL;BSFG(Tu=CCqB0fH>$50IAbb zUAtSKPHUR`XUp1~9OumHDSI?EQJc3HqXdD9$GFuQUn&e_dJeT4+?H%}RObM&%l?$r zG$&D)lU-(ArLuj^MB1J~B%jW+WR5T~*Waa9l}_K3@;VwlpI~(jCe>VVjIMnuRMaDU z1|%BiL_{%QWi9M*C|MAI~e65Yt`50fvNt{$KL!aBhOaBaf9tnX~y2Pxlmo2FLI!D1lD!T z?<42s1KWzOXACeK^{UM?E0S@KtsJNuB$`Q8K4HhMYN(n)#^Zz7)vquzWUgvIkvVOrQcp1(U`gV)}hJC7i67{9YW3vh|4wvgMe4kvE3>LvJ9CI>@X{ST@80_j3Cc`)o#y6irtl(LnMKE zk%6CTms%-Z4N{fPMA|w=#arW8e}Sp~~ccI&ayIcX^ol zEo#ARlS{Cv3VA&A6tdd{<~7`>fWxIObeM88G(gxtI{yH`m;V5-NVcTpxkL0A{){PAVe{hS(x5#P%T3 z%)5`3keJBN&fkVAH;zVO<;n-HbNwp!*(ClbTz(jhOPwul9E{5l zK2Psl`fzGPXytNcX8e#_0SU6PyE6e`HSC+K3OMJx{h>%Jyu=7IHrb3mtRyQHa9f^-Z>3AG9g#|l z+^W!BiAZ>W9g4}CwGFa5kj26FIp(CWTOCDw%V%&1+^H%uarHb?eEU7}Ch!Uz6VygP3KArXuqMI+LVVB{zeV?0Rg54i@X zMg#zNOWVOr@ZRid%X*mr|L%@SlVndzz4C-N^G{^?1!G9uN5*w z1c&7kANEyKZmOwFDuHoD$AFU+H zP=uN^+(+HvsgY_iMz~^w*UJoQz182HfeNxNIl^S0&Y>rLO1iNy63HIu)-nfuqk&8^ zIU!kN2iqXkRJME)g_QpQwBUY#Q`K+bZ;_(UW^9_f6Lwl#Ss3mhBPU_lj2|Matn%~OC`3kYg2T&K9S7maSjL<8UF2#>!T4|0s8*x`4_RV6; z;{7@@+aR7s{DG=R#qvmm!Kb)9{o_qVu{(7-q%bG^yGY;gEL3DZ?@aX_aa_sP_1S_f zHgTzLLlyr36HtAp#ZpRDj8U(sApV%6mGu`g=ym~H_){mdnNLSm(P8{x>qvRnxvDgUu#|l zNDYz))Ks!*7ePYFa;?U3laJ+2(Eeb&x+8J*8&u)0G5*Uq6;A8KT4l24Ti8$!a^J{S zSn%S>gC)q@+@ldv+vxUChKvau_1z)h{|H0b6M8z}kUl_27!iptw17R^CZkoe>IRdYhZ<(0@&#Ke%XSWEu^ zZ3rLIkL}GuLPT0>ANHA#^sN%Ph?KWxBfcerQjLy`eMV2|OPW0p`CoGyYn^^m z`?OR~L>T^gs;#GM_X96^JZGutT|fFk0e;IfCv{R+KaDjfxk;DI(q)$&9i|GPetgmG zJcgm>a`Jd{P&j!m=4R{$f5xmQhM8k6F6SNhaa*$7+#@;u&529&klm_<`g6@ud%KwO zS*+ts@?^UWpTR*ids%eQPLk?VIxVfbp^`Y4xFjF&tqWL{Bg|iCLH*|3N&GmfLsx<3 z7Uo+PF2IaME92jG-j!vwHif*inOCri&4~QYDqQ6BV^q>DzuFN27f@raJiq?EK`LEr zR(tZ!dn$}oSan=y%!25kJp8f8$Kur_y2BI?X<;)yI?Hs6kHluBmOLfVgL7W99P$ij zkL5(R(kuvC?q_B>1#*B7sj9H*(=?l=wYFj1eCK1Is9J2dw{trxS=uTdnTkmd>OiI; z?`d|0LNrgmyG-hT5-NAn?jp2|t3)>*2yN^>z3VVOtnGm}lshC=XuqoyQWV!DZ!PD% z1002w7e9bn8V+Gbi*)lD1^1SN{#oqFKaMCmE#$fX08V)mJAg}Ir2S87Snc&t-dkMU ztY@w+PJbXOCeUPdGD$lzA9bXW&;HFqzfg80hr<^xK>KCHK%bl!0Fn4+oc@RmhTPNGCW*`g`(C4W`<*QKk_uZK$iC{B+aQFWk2JPJ?T*Nwqk`FF6x# zIqy|2H4h6U!*`o;CP>IE+5Krojb^E|Jklpzt8M4-#Y%LGBC0}5kbdcNllsx>7b25g z&=Hr()1{Ds%ZqW9{PR*Th_uqIA<{01KkNnmREuS7GE&i(EJ!9yW~KWqOA!LyS0^k9 z&VMR;6U}0=@hS3{#ilCf{(4A%QBj`|-FaIQ($+TUN-C53n!Lg$c6ktIIR%#lQ-j1w z`J)}VIHJg0#&mklu_g_;J6Em(0sN{%sOvWf!LAHsjydFiJMnrjI z)C44cd{ian+^5PT(C|N*r*^@+8K=Xx0oQKx>{*ZXsbTQ_j4!)kW1NGN{Y_{eGowBU z>=+6|B%M6WW2px?pjROpJ&8Xxs<9dW0G42P`ckf$VG$*5BM03@Le~ozCg$}=Jp*F{ z^Qd1}(_treqi^^P$MmAZ<~3Dq;!S}gLHG)Pnuv|HhCPjQviO@!UYCsD?%U`2)n@T^ zf`V0Uq(0H2kJgojl#Z!x6yy!3+){b_NdOfdfR1a5lg3Ch z(~2}&@pQ$v%a7pdOQS-8WJ?TuGaQ^_1rOG~K(74lNa7aG5qt7_x^HjvVlau*U znLy-cKUz6Q%v@r!ozd119k z&RAd?6dTx1X>L3-05R5=aXtQ~svSbkE+mpiKi!Q-^EF!MUNDkjf;2t1o$dVqtz(9q zjAe?ZjYTDo%A}sy8K)Sg&O|Z*d+asMO|NPaa@SHQ9Rd=depL_l&Ee~FDfVY4`I^~a z*Yh0j9oi>cmh+$aXD9veX!|sy8HPcghheTrYWJWPTZs_*hQ?}9t=-3smraw`bCXZ% zYx#xs=lrKm@j89%jy*98d#7`4mu)|tGCPq1kWd1m(MXkO_Z7#)`zrGJ2tv{*x zi|gJ-vAK`7Iixph(U{;?XZD8_9ldgR`~wf?QO~Q`NTX$;jl1mRex%a+t%vpB zBT(Lh_fJ|+wJ7Pwtzx#ZZJ(CW$@OQ)@)XmnMn(iAA5i9>)ra-P)Y|(mgde79;&afG z`BrX|T@6)a`kDrI+rT*ds1oO~{A!St4y0iE(u;^g58jOW8eYxzAGKY}o7nfj`p{gC z3H&Oj+AZ)mN%)Fiw8A;v!w%$9pHW%Ot671AA!FPeDT{E_NswpBxjGprVGFu!9IesfoAikb~3lr^+0|_a1SN@u@uEI63FAH3ylT^`zWSQ^g#ia>$XJ?kNM*no}XheS02; zss?ZG5&BcqsW~ij`O5?at1i+Y7*dQmLuDWKtLlsedy&MF*fbl zE&x5VNLg55I*N~S=OItEGkM7v!1kk*T*Zy!5X?IL48n5w1TGDuwK(xxl{Ac{k@joy+N3(ZRkH(7y$%V(^#WjeOVDNoK zAqoaFO0hc(s^{rW$SzWvIRg*~rHu<55(QlQ4AQ?VkN6N3v8QPEs6>~Necp;Y{Y@z} zmBJ~uVysE`dere-M!flB>T9LCI%JV~P+H~3-6)*?bw(W`N!aCtYwAu&{3;aF)t2Ea znX7KD{HnL0tr+x41VPM5iT=^WY{3nNkq|7v$bX54HA4RYQ@7L{B0Dl5$pCOY&1WjQ z=u)TL&pLXWp$DA1x}Bil3T6CSd_ctw(=>y*UMo9T*A*?iiy*NfKQjaKIQOheX1ThT zf6b2Mp0v4})O@RBQP87|WAHs{QjLOHN}q8=QA8sPG+88kujy7S;Vt%fP?IV7S=^Ej zHBqR9;L%VAjzWZB_fk4lwbNuV8CWs^0VT80{{R(rwmK!eOTO_EX^H#9bQPs1kW*s? z#xN9))SYW=3O&y`xUy)ZjOQsJ+#8WXM!*Ccx%K9{i~EZeDHOy=pzyl`XO)vpjxK+L80ezfSblveV>Mnf&M@Z@PD6HBYun za-Am15tvNt<|gR#&eMZix>lcWAZY|F<2-qTrBt+iF4|8r0$xS?rOBy|zU7EaC_Sq= zROZndMw*GJwn%OPcu{fDl|?LiHMBQob1W|fd4e3-!Zu5{|G&y|p0=XW<@M)-AZP>p(=yFLTj@3;r zjB^PCjE;cTG?y;Y9gOJ1w-~6_<)dt~2449GirGNT&r_v))tj7!^*9))h3A%jSu^e0 zx#{mbrv+rd{^>OnYHA+_LCCE6WSLjF)}Px=Cq2oq{!=G)M12!9AkI)iL3DIs!FW9f$v-*Y6{JP*VxqJ@F>V`Datn# zXJfvbSd2&V4!yBi$5N9Hd187T^sZ3eoB}ytT-31LHr}Tm^^BsTjbkR+(2!<~Zcxm5 z^u<+{)^Z1xu~?tt8K&9lsTLANJ3m_1pIEUGqADve8RT+58mLsVB6RL#eU@;zCN@5W zYG~kKGSGs5**L{$!>L(>a3fI8a0%v{{iPFmidZQfhB>W0tL#Uz^D797kD43--I!LL zg`>)h#vqUr-MalnRzSLO3DvCs*v$HmQW0Yf&8hZeMa;xycaN<@Hn#5zNb)}n6fSw^pdW=6ZG&s6 z+O5%=&f+z%idtkvzjgLPs5`E@GEbcwQKgy#Tz17P3Z4Ano#_gPOP(rPLeH;CZw5x!Q(PL@<0C-hJg@$Rl0D13;>0Of%=`V7)_WcCG3 zEyRx9R%>(-L$?J;b!H&r-lThFM`*>#Rbhr1`={IdDYk9pLorF-;&Jz#hB)*UnspMA z%4ZTY5rx1Q2kS{GJ%{e2V^6hKf!0|}P#j@}6?kGpzX5;d#tDh6zqehKr1Um?kmO(duz#61{K`O}KVP#cLqDV&Xe38vk_Kgzx zQPxMJ$0;m;Dg*bi$@*0tLe=eL+LsL)u=#L{n#PaB+Jnl=wsR{EBZy!xze7@8UtU}g ztA168%S7=81N!6q;)_gv?&XN1xDYq`L%SUO(m5ma6&uBe>yCYyDYJx<&!;6YOAQmCMy%fhZrnX@<_+8YCWUAp~#C_ zirZjiW_BDV;h*PEmgYF4E|$k2XFI_HxtpC7`GPAQE)ufx@=b8aG5zj`KZQ=(75l{+ zJ%*MTF2Na*hDXx_^{IBbNoZ@_wd~g3X}PtG^-y~M06M|AHy6+qGXmU(F+M+@YB{va zi0-3~O9WlNb)>vy1L?;~vi=;oY1(9!t@eQImh+9I`wSCJX}FtaL|PhLJ3EQlI&Sk1 zU&|F7o*03EXOc+UJ$E-F@i?nfXKiPy!mCgi7}HQ#{=JvKN{$uzSZYp z<>^F~`Y({$;X*GYnD58ZpWc}n~3 z01wi&u52d1ia2g0iIG0)#{e6B1!>IQ=4~VisOYlBkv^|($m4)!<-VB5>st0&ELPUy zTN_jf_qWD3?)n__Sc5>dLIkie$j2#+kIJps!ycUun0sN~uL$OhxMsCXh z`!@ESLm%KT&*SM-w#KZcN$nRMNFe_JI;$kx(5pMLpYO3h)~bD;)&BtHoJ5|)14=ON zQ=em0U06oszNc)5*LbF#Q%B!{s4KAK>;QVF(>xz!;$4# z#Dlh2xAGK&SkzfQ;wyVXTl^{y?FLoOC1L6l{{XL0zN0YUTTH{(r}L`i4r1B4)TTgy zvXlHu6%Xs0iDlInXjz$$BQGyMpsHWmQ@en+tfSSO9DWq|=em)O$X5ff1fSB5QhAGD zT*wKMb;hTPxG)n~h%2hFv4=n($^S`tYm`NZx$wqQ}pH!{jt z!i=l?v>)t703U&=c1AI<$TDzt41Xb1%$=IyiN~m4=|hG99q!(TF4_F3m^L$NXvrw6e}!Fy5@>{+Xx}3199chn(P{{-&mQZka=QPKr3ouoQBj<^}R#7*-0S zpa?&ysUwl2hbwHY#?o92AIh`lisX{NX`h~eNwj||iES>3-q#0#(9T$&%8MZ8G@41! zDN9t0dvV8p)gRht+5iFMCzB(v1Nl{PZxI7zeq^};l;`>LPi;QogDhoGb;wSh*~Jz@ z=3|;x2&*Kps~q#!ADdFFv5%D8p?^#d@iiCrZO{w8P1!#&00I3f%z8{pOG|QHk4(1% z+MSXPYmj}dAtlYj72Vtc{zXJJt1a6EgyWNrYVoj${I4qQUY-8{T4A$|WCzUS9RBPA zXwY{nuCHwB8A)(|*eCi^Q&CADY*w!(IR|Dxn5(RigeoO-&uj{f(%iNQZNu9%?u0;> z*DTH&2VK1Z{{ZU>f?Lb3G36Y$PILU~;;b=(5+(yV`=Dl`Y1S@6D&+Mhjm3KrEcb7< zL@}`~(3Tkbfl>fhL8!h9YnE;1WI{VCbo|XDT*%L~5znAB-#T)^K*0Y1b04XvVRI8S zHwvtv#=)DC>ZcUPx43K{JAvq<6&Cx3$OcTQ?Tk}RrNgOWNczZ6=qTkAk!Ym;R?~Pm zJ;n|_I@E0yvJA;05<9jJ^{W2>U~->zGvDPVkm-{KJ9r+UK>n3nqUI-(?$8iS%5la5 z?)*AZ4N}<@gb%cKSh)U`3u&^FpfrU~QV&W7nl})}MmhW2vMA+3<{sLzG5-KjI}X`k zDj}@MxkQ8y_vHTo0aE_}X2d%jdepC{#y)MJ?nA3JC6T(zcD%G5o5bn0CchmiTTeu#MkA{V1^1j8hrO z$l!P4pm<%50_WPb+IdNDm3E&|Q8|>ZOl&=NicTe|SjcNf?hn%x!)U~E4QepB3yqu; z!Q(X6g5mn8{7o)IK65kdjs4yasHPit&N0nueUjn6Ql}@kdSC3zl1Am{wrP7X_J%=$ zfaes$3ok#`wvL-FK4H)M)W6xMBXci4rk}D8XkqPOef_DwGV{%E{{UtBvi=7&zu4=7 zNzc}&*$>;C{_=Zc-h&~>_*UW4Kp!ZkohTfI1pNV}>_JyCcehj0m(Bj^{Oe!)7_d83 zk~>sKMV*EOE=>-I{kk(mr@b$o&tKNBe_-kbu$Gb&>JS}GChjGiJ_ve4a<=FT?Q*G)b;c@gpXnuzNbXt)-R$FY)fPviw) zpEDmb9Qq1(ma2LAv)EOtaa|1EX}+eMo*}pjR^Jf@7{FSh_m>wK-7JoMS-2Gj!QYQ! zX~2QN$sXNmlChhUdy*MWFsJHifGUE`z&+1XPAssDkP8lj{A#niI~FA8*R?TNbIZ8& z`evi^G1ya2J!qogVa3?@q{;6|MJtZLI`d90bC3WOm_4a5M_Mi`4iYfv#C;7Ykc{ET z&r_ObYACo^f66-YX}hv`;*yFk78fM(nqDci;4#{OBtWB~#Y`MVGFt=C4!?~;q00Tz zPtu$royG(%f8Zvkm}(@V@z?m!w_s5~>HerOh7Y za?7z9o!bDregc`lI0{?b)z9p?Pab4r=xX+xq}hGGAMH8i7JI*Y_HZ}j+cc$8HY?dr zv4~xV@i*s9GB;*#&brSKX?`3RQU3s8J@=Eh<`*nCXYdu0nr)~bHskHOrZuA_b4g+6 z7h@o?JxToOwYn^<*Q7xGNvReqbwBD;k7|C^bQiFfQvfdfrEv_W{kh%W=fSTUk ztM@{K?ZpC8d;Ya9tL#^>U0IHrUEA_dFZ;|Y+SJXUg2mgcdPWAK`7ae=~t zpT>{1l=eQtvxAe=^!KDP9P~epPqRh)R%kuZVU5)gl;E&(eT`J}PtuPtkK#1c1nL$< zj1LE`KX@yQ^Y2u|oP(MSg!9MY#TU=6Tv(RH|PHVuTPXY%OS^L57Lh}CzTx4V8cJ)Y1_T=^`*)_VtxBgPT*-wpyTHH zdX;Edzilu3?Dew6~rfyM;_LG}7k%06OaCvlo^ZkYnB7a04b z{b(|L!;Dhp4syYeZtKTN3%5XcH3spH)TT|`U{T5(T*4(tb=r^X^RxWw4e3 ze_HN5!$0j3k(7;@y-xsCD>du59#X1zCHbwPYOHvSpP)4FvW#HIC*p7^4H@e4bR{#6 z#+vPRHu-2I&!-rz5oU+8ulxxA09t;~fB;tm)w!fIFE?9qZAm`F8jw7oWZSqn{_SXn zC$fHGrV%+@?OY#2MuJx{7(A{1u0j6*0W{wtVgCRu+>iH7Zy2Uk+v#H>KTJ}a zIpiNSY@^+Pt$;#~Gsmd{oT$>NF~_(LypAZk4NNrCSsNk1K85pAuAgy|M$zsKYBI8s zvL($(E$&!l)C~4h%`2eP$`&#KhIrI^lS(19U-g$6$55iQj+k8h(DT)PNgH3-4x`jMpGY{t(3F1un|t>K|zHTl@_mcBqTzsChk>(5C^qK zb75sHY;*>dwV^F}#o z4+Qq&q5lAcs_yUX@xZaF$lofi19j_AOQl%HiFJ2@dh`L?=mm4P`u>e!D@kXk+)Z#e z4%rEj>^jxP5%m`|y$?XYym($X?ipk%GEvkJbM&mK^%$jht6?S3;Q z=+cm?w=*;Z1@!Cu>oQ*(UClV2>ek(u^aW0TooyOvbTO3mwa-C&r~{W*P~Y7sG5J(4 zsLYWJPj4FMfy$cWhmCi}toAmcf#+iJS4WI2FQO6N+l3gxF^)x5(j%3fo}Sr>p6D!$ zpE4ykXy;; zNUXtn83@4t0PE1{Top?endXK#-e|%z`Wza1-fms2NIsdWvRs?ko#Q+4&^7>XYRyTf z0fNJD&WGkgqmQO|#b@k`Rc>lE;>6!Hpk(orO84ov?+hN}n#FjNQf606nA~*@83X(( zwcm#xTTqEL!H{$*CeS|)l>Lw@h|`hUOUT+bRRHoy>rcJ7iXf&;?d^e+ShjjTqiOdI z)Q)(}m|(x9B>G0G;D)?X=b!a#G3}8}Q?R8pSiHYLg^_}Tz+=IxX7*^*F!_}9E6!=j zrr)^x8(WuNrM4Uo#MSFh3|`yFtsAmOoRlS)=lWKbBb%|6EzB2d<;yCd^k6|#R8rnZ zoud~K%73~~AXisEg*81!CP}4+G-K75C-XR_N8m?_@nsjgnO7UUz)#`9rD%oZa%S53 zre*S+#3{KiJl z^rF!V$l(ff0u(=58XVYqD@eiV5Q@AjjoigKOc-SQG{FVNw0q#%gJFT|Kal zCUQG~0HoXja~^*c7n@z^>7>i$(z=h1h$$41QG%z8<%h zJ+fO8nftq#1Nzj;Fp+%nM<M<2l(0!QmpfaxEbb#kc3qOT|U*F|sPO9;xC zw5w&>cp*kd<62SZcCnnUmu6eJWKw@xT(FNo=9}tr(RtS}FWIJIy_hH?`PA#CUF{p> zkde-I;<~$ZmIf%ca7Q0u7z6oKzv+&0pC|jpK%(huV6>i%oH2`1^I1d_5DyKKTXz_Yd(z*>yh&(?!#__C?rsKg_0VCJ1TC*31)mQhD<-fhO1NsgtP+Ek^X`dhOQvIGE zSsUtVZ`i98*GGk7DASB|<}h>kntAIy}J4(6DQ;wNqG!+&a05z`;$sM~%ITw;$gXNF`-y<|EhDZ72IPJHJqzYsGTYXD$ zQlL?cDbKYh6bekdCs@~N?jGH9Q9qKzm4%s)w|a?WDloCJ?^21J954VL#EOesV()#( zg{vHVh!3H~D%i+}%nYjE?2OfuFjUSI6aCYguGf#w$X&7DBC7IYv`LxgWb(rABz)Px z{0r2`Dq8$+M0?byB0i)85A>HMx(dO zQH8e?GbCXoVYhOqQb*RMx3>M;77Mul02m+|Y>_M}g_9)uiY|v{Q@OXc-M{86e)&?v z<@DnO(~?UlloVZ{jm%gO!~Xgvt|<8cOh`{qW6f7OYB9kZQ0H)SpXoqqh?V~U(g|4P zkO7n(f1ecPyp%}L$8PcO96v(8txpw#LP~`=?7(tA8nqfmn{diV_2kiX8k7c~2!WX$ zqy&ERfI02jqna5M#~R&vQGvHErIdah>q=K4WFBeS;{*=l>HTUbp)a(AP@s3tf0aeL zmvymH7oJI6s-rH=suXf@#(MEmv=T>T#r)5-NZ&T;198}JPB^Uw;{n+@9E{XrBxb-1 zOP8qmiZD9KaS58=%PYXj=Z}7I?ZsXG(Kgb*`a-kFepteW+4uqdXjk&1W z@-Q(O-6yCWXmLzRRxKNe9BwS``1{A@KmBTRO7px+i~^~P&1E1#PsPtDvK zY?97M5v!zUt05U5ohK3V7g;U$?|CJ6U%Ep!2e78S&=L@^$-n-3MmWcy?OFS-iyX9N zLHSECElw!zS$xziMlrgbg>zY_SpuDoNF7@k#YAqJNFT}zwN4kJhWeBJD>@Y@etCgF zKJMQC0F6$e6-WRT>H!$VD-PwTZY_M3GX+*-fDR7tpv6UXCA6i5v@7xu92f^V_o&D$ z;BFZMf)H^-JgzyJ#upzh&?pg_wy@mXGt8317&y#n_r9K$G16@V;a=c2G67+O_<`1} z>=NN!+W?+R{{V#wu%TmQy3MlV#3tVgh*-7atx zN#btC7QR4t63BPPHjj$UFxB z0CzO`P;)amrgFdplODcbAI^YeBP$!6ci>i-*rz*}aq2h~9Ggkc&4c~ZO(ZL_qa@ph z?*5>$pnfYc+PO3XFO9DBa#f8&s=~` zD${wJav@#bw`mErvPB~8K49%#SoO__RcCNmOwX!R|kw2=~fyL zSE^)vMKRU~QVCMS(>-X=T9sv-}(+HyqKI9P?p!r;wbXniykV!kuWRf?Z@|HOy}`6X{Wj|gdRdS zs>WFUrmMBUjD&TTV~$EDH-0?@Jw&asZUYLOy@N7iAd)}Ew2>`a1uc*h{?2p%0N1P+ zl%JL>I0EB?EUx5zdJ3A>=KU0@k{~m{R3Hz-CZ#6AT(LKziB?_ZLk`FzRTX4P7yez4 z51_0Uy}RHS{DvyL*AvdAT2GMhM&Zp@DHl3cy;S?&Ql8&1s_lFy&y{q^jtO1|=0$Qs zH$#*nGwp$zf@tns%a3_*>7?Z-570&zE`@otu*i*q6W0In!Moo7Fc3U2!~sEa?us;YlM zS<>mdTa{{tO*sc+pGGvCN{fj-=ZM<&BOLatG3?VjHa`vpV>QYV#0aO=YT?sZ{nY92 zQ9hpIj8yWn?u_^H*|O0tey24P#0zD2Av}Dhw1tb2z&@QR8p2NCbMH^y6{2OOvW5&v zC4XAAYvo&9`K@aO!2aaPxK*FA$ae653SY3x-AEp!Vvl|3G@yrGUE19bmr1dN`o|tC zJIH~h-tc_A!ns<}8fC%bY02rwT2<2KCkLtZ%_Yu(`!-V+x-r~jvbiVyo1A|&s&_hN ztI3;FENlJQ2yfQ{wf_LKCQu1D9X{!&wvQlDmh0_BrWdAjzu71LdW?U&OZ}9+=0Eq% zbV;PUHaBuTML6i}2qlg^g*!~2-JI|CTzLNgSbr)8l^1zm@B+G3(SXAeaC_sW14RR? zaqmYg!OEG=jXo70dA}M%r@`E@KU(Szh>901GJAE+Ou7_6u0!M0uN3(u++6hg9FVZK z$41Zl)Zt@n2l~AINv^bLLw_le-xQ-lPzfqVIvjCFB)fvC1<>S$t+@XHmJ9mRyod)# z6Y*N>#)lrvmCw_GO3qxYbzpIYAs_7==gCMLVZO1Mse-=@$Enk}`T= z9Ey5q(PJ6>){kT!-5jWA`@4Ad6lO!l39h62HXz$V6YeT?)6l08DC|iTdm#4xjzlb- z)c#ZmrDMXk;B2LK-hA9Qiif_0Ey4x#U9Eb z?U~4ZiebqFXVg$E<~;2nde>ew$vDB?pKtM}HlFL9r#_ehk7kMX%;SE;KT^DV3JsjV zbo}eFT{ZGIarG48rHH`J)A(kOW`o-^jTTd6U{ae4jN~qUhPv^mV+?s7giv(YKmZn8 z9@Kj{hqh-O_9?n;$@HZA9N7Dxmo?e{0B6G6w<{jl??^P;WlWs?4Ia)T_N?Q6$uQ`& z!)-9+uO7A93n#Z?U}`!d2ztc(9mt=$K5sB{{Uye03jEi zfOCprv4C>>IsWc*OOX%SvyAd&0DOn?q%p~!RF9~x#jS0}@RC0&uO+Mi{E_jVa(}{) zW{2%r%588?0jB`rbAoH7YghsQ08&nS_NX4#4E(8|K}*?q^_ z6u)A*Kfq|C*|GaXeIK?}J5J;DAN_i;u%fQxBQtst(xecG#tey*{p^}aRrYNYkU!Z_ zDCW7Eh}^K@6yw^M=GugRZsLdITA>7{(IEz=5nMUBfFE$ zKw1DvmU#F70CWOqOQ4>tgz7e6fU-L0{hF0$vy-1SbM85-cGmXOE@3h5@3~DyBncap z54n$Muxgkmkd32)d%q%suF(3I3B)PAFeA}TM3!)cgWtmvrx&WHhFgq=p?|V zxq64ZjP%yU!WM!h$9$HPE!AHY18Gd$l#Bx^O~64n0RTT6QMSxy%o? zK6lGAdjM)hw7H*XHq5|uEV=%5(?u2Ykj>>F_D*W*TaZAO;16J+Q**aKRNLxvKV`a& z01-&Mbq5s$)>kgM+~oQbn(Q9d-M;d(`{W8}wYA`Eh-aUgr`q`q(tXY{J6n_l$Q%z) z4OosT0o*qq!n&gsiOy$@KLhN9@#(2r=Q4tkGao@Q7{%)Bf5oV6&w)G(wB?D0LdF(TWGWW=>VZ!^c}4CAd<%6{#h*`WzI%%oYi=A z`4&(k(_@S&;rzkJ(yj~nB?HQqCheH8)grC@fDO_N_X-E}rXs)gFk}WmtC7y)T9oKE z?HfQ=0;gcM#OD94|c|XN^?P{O2MT`Bwom5JAXk|;L>(Smx>Jc+{}NSSWQ0% z?%ILd0MX46B_g6GmNAPIBybBcY<_iQ3sS!&x6S_O0KiNu2Azhd^4l;j- z0;8E1%OZidS0gs*f$Q7#p`k)>QaI1AmI6Jd<6uFqWVn&)=H~`M&y_TdF zkw!jukoxo$9Mf-HyEw;W2D7dAp|hk}#v75rI;ru?@OOi!b+h#b;=mCn8tW?cy0y+5Z4$Fh8AFZFLKBvi(UF za9dlc`>I&|PARV(MCT`uK~LQq<|-zxh4QgXf8a)?8mY(nl0V)w@~ERBOBU;v0-Z#HzOF{$NmHbLiYy@2F|@*^HqMxO?PA|Z7Uy{Tzyzn!qz-; zfAQf}%(EVb*n03yJV`Gg+%A1laf&tc1!ClOu?**v>&dCYi~-I^wP!|(=Pz?Kg!&v* zs{~{g8~FbAO+zTSUBJg<>M4dv3CJuyi%@`|;jK*RCgnsd6E)+@lyzg7>Kv~$23_7Gbb~zRdBzT_|#>@ zfbNmKMjUfdy13~aW8W04k}~8pKzpqk4(*NtCQYQSJ@RR%?8rX<0N|-WP6p{gKkm{H zKJMuo+ze0y)u01~m9yIzrSm{MTc5;=kIM(;DCdssXc0!dtWA#mF{0o(r*M8qVznB#u5p59?4o(K3LS3a9;mPCq^>7St`#))=Rf zXHJg@4-ijTsd=Mebv54Je|W~sc>M=1e!WBA?Ke@s-DGwxuZ zDH{OCT>WU~A1f~jrNIb3W6?|00{w)ZQ%MQq`0?O z!%rfiLD})RcE%}l50ufCSwgG61ZUWqh+M2n9iuE8?(@O?D<(~6Pn{Mx=aqmRBFi8C zwMv)Pvc?F791aJV#5d|F<^>%~7dNiuQWf_hamV6nHF-9Ik{0K8Ex{j$f5Mn+xvWO0!I}!acLSW)qd%LVBk^m=>`?5*=@lqq)lbKF9=s+94 zTBR%MHwu5#<4!t6nEg(3R3^6an=@a|Pf`(!euAE%q0oY3Mrlwm>_cN8r9_g;EPo_x z8QuNi&0$3}D!?RKjln-Q8CxHbtM>BTnKu1};g9%>Z6DV(ton-O-P;=`?J|Dw>yOf> zSz}f1cXPnopU$dmZmqRXZU%pqEODnTB=3(^%~vS7%SP%(+6ZD)oNkjC6>hvq@QEPXjqa!LpyFAI2fZrYE_aal%h#;zZ8?g*t8K}b-D7trT4wCFn!JjkoKNIfY$ z!WaX)2cDg3^4a-pTOc1y59>%{iL-*o9Y7iWd{J_&XP+<(m}D$R3y!1lr-YSY+#@V{ zVa732tWih<1(`9CmTUw0Q`KQmi?xnB?*9N9PT*ECOB`Unz~pcjC-AAQfqm%kG48F9 zH&n%9+TkNmTkewltx_=z64!F%e+VL#hjH>jJ6tm=7~sbQ%5$G!1!vt{nB|T;tBEq; zgo+6E?#KH_ujg0s3Pi=^DaY|lK|K3bHN-HmSF}*lAq=2`%uoBoQd^DeT3Omzqw?)S zss8|L%s+`8D#0#X;A4yudJm_)WF_UqGat1|$i)k!Mbsb5KmB^L##{S;3)~F5N!U_FoD9nG=u{2mNUlPTFbD#G}sQlE=3dO#)P(>i;%K&-xGF5UqWD!hRWEdl89)xjFc``2nhvKA~kVe3Bxc(TT!EwgqFDy|D zdJ1Wm9XKCOw7yvMKaD7e4n_f|U>YAhfuD0x<%l35>(Er&anzsElny%nS)d3+avrh% zZHjurLFfiO^H0Y)Kgx{Y{nkG80N*8pWWUoVifFZuIVU;mQe36d`?j+}yV_)^Dp9K(Dka`ejZC<4il z9)MFzZs4wJBP)M!4{~TS$a9^beJR)pOFw1D;wUB5Ws0cvECo}FD5Ga}j7QHqR04hJ z&XCFX<1%CW!!(DXD!-V-ERI0${3qx|J8A*TWOwH^S~q|Ku>pnZ0|E!-QU(KrRmZgm zmOQLphw!F=xByK* z_|2 zEQAC1lnR1L+j%9JW7t(#anX(WqZo|_!^!rf61i;PC?34jOhlM(n+LdRlse=9xF@&& z0Ir%tkc`rT32}ky02IfNmixmP?L@1N0*3a^D!JeMNB}3frcUJLi5zF}sZcX>-1f-L zQduM{au<%$j zB3rdNVGD8VicOM4@aJw~*ax5oGvfh=3+e18{4<(QZ#d54dnWYR|1>D zc9Ip;sO0laTS7-gDtogZl}0qR zEL$s)!(es&YeJ4E1|bz#%-sSUhg0TQ!5I*KkDnP*Oms(^jCrmmG2M^x$e^U|+~ z!WU6mttxDYUzElO#uvQ%q|Z0}Q6+Jq&BowjOAJ#?kCENUsrX z&9O!}0Moc!$a_0W5y#m{OUtzYWQf*<^uyD{{T^1QX)#YO^m(6b4$XFqi8tb zfGE2a%;8`-3Y>T2{{XE^6^I7}DIUX`y0bG&gAgpEs612Iob-uL)=W`$27Br8hW*)5 z&Q5bj_IyW{Ku4}CLnM#&E0g}#Dj+2}F9Q1z;L&jue#;nroe1c>bf*h6C4fLrv8&+; zIXFJq6&5!QkA)vWPT*EsMPrXI^%O~HiNQdv=xW)wMOFRcsQd*r))gN+Zv&?$i-@fq zvU9j)k4^!_31U>`n<{%YKb9)ulyilCNB4OmqLyf)krCP1-=F}W<(yG*+T>!_0BvAL z`zIKuBngsKzIy_Ar{O=m`%hexPXT%jqdhY~tVAIM<>Md^aZd6hFT#w0_|ome4ZNRV zDUvLXr5TRjLGu3qAxluW{{SIzl8Q+6BvTeB`$jqSCp0T1v_V5Ms6SqR8X1d#3+K>a zW|#`e5WqMM?MyOTvTavZ2aX8G=|<7e=7uo@~BK;Qs*nsRH40zpoSkrdU`EBA>7S09u-50fyBW_5{-k&Sp}4lNLI1T9#Pf zU@Aat{{R=vG3|_VwIbx$`9ezkwiDvvVF+KFqaLC4ZW~4RaaCbV4xr#!jZ4eeo}eo27oOr zv*3WGN4oz2jYjP*6bz4SV-;8J(`8D7`R1XL;#Y1-7(M6$_m?EfxR+@iN_6~a@|{l5 z;N#F|`c(&#ZG?H>ulQ3IMgdOqo|&L`Mux}641kZWDY#vrI|gI-n>}il_UP=#@x=g4 zSwcAaV?0q{x+Y&N$k+z}{{RyM`cs{EXKCZmR81Vku&7vfz{N-{q`@FIedw@rA{U9a zsRWTt`#E4vah^C8buDK8AaUaEuJy?;N5k7C;2w_lq2P}W31z)L_Y@G3odQi3!zupUq*z+x8To65Qc_;Ozz2pNp zL=D@JC>7b7G?zcZ9l^Pqn1;Hxtr)Is}i#AIXLwOm6+~}4GoldBn`gCn8L&yEJ?ezPc>Ph z!*1N*dUm7`k~6ipdYUU=U_O>y~Phd?4aG zV14%DlyEb#dF@=zP5B$8Z!!}Fs^{iY^#1@8Q8~3ZS)#k6&bBF9ojmPw>sIp4FbD0^4$jHa@#Y;_YQnx|G-;g}% zqKF??6%2O}`Iwev!0LB({VJW(%`5Zf6W9U%6$r7MoP>YHOYE9^q+rB!>Rg0SZuigzHm(YFqRSK=c3#s5Gb) z64MR8PPFr+LyVc24x@@zhjFJq!ac>IB(~yD`&p)&>r_7^UsFgdt$6+G5!2G1EbNf{ z%*XK*{n2~G5?(_mNOA8`Z?`}4?g{!;(G8Nm03>>oQ>(y@g@qVn`?;f;bPiY4&kt%o z?V)4eBZ>>gPs?Z*(+gML;IPNp`R%m;rio^LJgui4*a}?1%2!8r97fv*P^bs%PTIvm z3nJ&$g-jj!Qm6p$x|_H+%HaUdQruGJA1NNiBj%F;`nUK|=S2~~az}Cr{J00T{1t3(`}kEdF9 z3j#Bdwln|^q)s}DJ38T104B4Nly$I_ZigQp(&#WVMWeA6hVEfF^(?*YeZYfl<4K3bo8fW*h2 zv}4!|(@ml{`Q>)~C=2!^Yt_a>sUPg~QGK-;vbp@R{OQYQ68+UV_a>wkir;u2*ZgSF z2CT04sJ}C?KGj{d2WI`;Wc@+KYDa8}I92>{PAwX7y))@W&{{@QTwavIK&*WLHDAov z4oI|sE_mj*4AF(iBB3*>JV}qD=8k39T(7y1%dWBEVL|PKN0F0)C4} zIC6I>MW@3Mzzl!jBCIX6XMq^}s!?{VdC2AvPNOIe%{oIejk!{s; zmUGW?1w>?#^4@!ZJB0$FX$o>=mCv!y@)WKXMY5Y$rBeC?HK@l1s4ucaKow$eJSA?h*1iR_iid3>l5t^a(~;Y1@e=! zIS{b*Ii%PqmIMyD;2&jRKba?=Y&<-=sufmTrdwkJhTbyKNH%cZFE`NxSqJ zrFK^;G(>HU!GX_iO$H~y`=9VG4QGFBheGPo6ej_aGDq^JO{gqeRQ;g={OG$M zDXhxjfJ9O;&fI@mKYt)OR35{hKRnI5`>HA4Kp6wh{P_KA8TC@KVPlxE=o0`B$kmwT)Mik8n3WIlOCuKifTH1I z@VAW!W`#pA!sqT9GV|a}pqK7yuvi z{{R{_VWFV0O#2*@9Al=%3!l%Lj#r7K$aHq-a>pP2dds-B1~y5q%!Kvbl7F3KTxpiT z2RCvy(SnG;oPK~)vlfR)^L(%62uy$9OaY%mP8U+o9v%oDJYW+bKgfTLVOd&QTPZP^ z!s8+3a@hO_8T@MG5KA+e4dgco{2-1L_s$5UknT>z$O=0_5)QzVk8ag9v~C@9u<^hN zS(=15@(~h6IswRNmuerWIjRZaoBPM#KC3W7Fk?p~kHliDl$)FP6Gay0mC*;Q{{Z#P zb636~jtQoYDU_~88aBgzr;5A(00>R8NIdx^xH!(|em@-3Bk8=&Anz))ZJd zj7c?AnOZw*n~7ul#7avM^uev2J6JZ+g*NjOE&x#;(ewqXSoDz<&gkv1;Bn?pgO6}Y z{#7B+Z03-&v%JHTw=5Isfk!RJ%r@g*-2O^6q$eXKpE&j*0p_WHZCxnCTUy2o95EyV z_#SF^u+yxW2rn4V4BsI?PW2+_@JgOyV6RpR-+S>Qj#tzi(N5n`)EYK#GR#IkV$t!R zVcgZA$p)OutW49_h?o<91QVYB0BkdM`UndG~S9U#cD$tf` z5VFex`w26Tt_1|xau$__>shn?;lTc4rHA z^{n`9{FuU+Ph5iUy#8nURn(8<$XtFHqREc!4i}#;PSy4XnAeA5p>;Uzo@zx*0m_W` zB7p)1%BrdLRpynDU6F}FaKnS`%}8CFp>g=)q-dDO*8|k?O<1;c2*=!-1~8Y#Fh}c6 zD~106SQPv5R`!50;Xw8jf+Qfag+GM=Q&lp7mJRKiYc4)r%Bp&WR+QGS(>8UA{zw=Q)su2k^xrJvhhZPWltA^*PH+27$9YX#qSHicGT)uV2cNYe^;DEO(Q#W9CO( zf$7fgdg+%$jax3x=kJCs6CVJb6Wr$en4|d6t_+1+XlLJwj2kK0Q4kM6bXU3 z6!yr+>qoT@WOE`WBjt>r!j)o_gNTnF*{++(VtJJQ0CcGu7{DvL@f5wN{fWx?QUUiH z@D!d~MN>vm-xUQAZN{m?k}r{DvStD0S`RGfkM z=8J`y1MG4Og<=jscyZi+beBA!+7NGYG8ranqxh$Ni+yisrz?%Tr^KA;XNA@+cv z7D7Eq$Qx3ou>T$EeK!3RzI$m){r_3&`q81sD(R^{JT~1IvvF&!_(Y zT7rPF$t6caxN?4&pa>Y2Q?@qA>+@7CBB0z0SY@1DU67VyXGg? z6v(+-=OB%PAMFuOj%0^!=#^A|5$T$Vn{=Nu@#(WQZUTeufIZF!^P<2_1ma0s{z zuzGG%az9FAIVwigU^)%F{{TvI9CPKVKJSm^nk*}e8%RD<3lZF!j!faP89C$(jkO_X zCzA>fY?J*dtjyeth|~}ENA)!9E0QCiAG{1sK43Y=>q@>-uquuL{_}tJ>K)QJ+GdkI zim(~{g&teu1+EYL4;1VdClUPHzTh3XIi+@qK?RjZd;n^pQX37iy@x-aH1C^$BN_G- zSXV4ZB6SKej-U>I3Xz^C#$xBUYNlFL=%q(|5B{}BB8gKilHTk%{b^V(U*8aH6a(t? zzGN-Ii2(K7Ijqkm_l^{C!ER|ll|eg|kEXysT6O~Ycw%3b7w8)w$WzRY*eVkq`9I-V zUTb3vcOO8c;+V;m+)%p?g9K4vxvFK6NZhDBz%<`AOKl~6`&L>w!vYHJ`@kRZp_>V~ zm(Ojjf_=HC$_9uws^o@BfJSYKV?f05OIh-{jD!s^l{uWBff09H5#n{{U%32h8LTqjOB2{w%9`&H(-uPrdoo zNa0<$-He`qnsJFya?knE$_%spp>Ds#03)!)BVWRP>v8lI0e~?8N_qbPXEcV%ParTS zzpXnV50_C;0YoJGv7qXWgNVWR0Gv}rxl%wMtuK;Dz|Ls0706#wn3W)C`F|Mu`XnB0Rp7<4^ z{hH+DmB)T*{{Y!&SCGGsD9|ou=0k$cNcGJ|+1hZ&ABnAn(@_W=hfEBAT5Q&_#Jdz- zWmr>x8y<`XDM^V@(jYNPVsuG2ib|I>f|8>_YP5jB=mzN!rD1fVq%??>bdCMr^M2>r z#o2!6oaec7NrDAVL^3@V`Z{wxpM|F9LdIYt(;QT zkCFZ!4QZY0jBc1~-ENbdNvgOeyAR%NL{WT*MqAnxi@Zj0IBTpaM6s?R?Vmha1ePv# zgl;?C)@FsQf*}=qXH{@lpf$zPpLh0AUuy1-?zLGgbAA(V&-OTr;%DBz@)Uk7lYx>t zADjeK&g01)Ehl1}$<&9*gH-UB z?@Cok6^=#qD;c*wcB8rdqecdHJ3%APT4eL;cCu}jA9-OD@ZDQ|LOK%(lJ}wN?-E=g6>3(Z% zeV3dKenl=;`_WqJ=s?|-Sn!wRo8^yUE_S-O3l!bPTjwQ9(Vx5JBBrDpisWN|(AT5c zYuMDan`W zJ>Nd0C9&~>yAQ*tm~E@$r0)^{h98(d<9oi+B%u-YbnJ{Q-wn-1{=)x;SS$`B2nI@T4f3x20k9qU?qLiz72a z=30LYb`9#Pwxz`$=v5@U487kefxxO!N|BtQ8ds4_cYrpuS0k@ z^B2g$U2d`b`yN+;TrZM4C(ho^f-272zty>V)1E{txi-L+YW&P55`ut!aYGgkmd(XF znOXG7jdQ}cwz*GYKRe?Rom0w2@p86w2IKaAy|2)I&1vsCW1$Y$tco)jDfA&QyAsf} zHNsH84d&?`5KcJ!h`Cha%x&AkPeh98bME=GuxHqz4`4((D3va-~=;{W} zCVqA)0-pB9$EhdStIB@o>}5fgrY& zoovd|s6J&)tp{6xjC_{SyS1J0dPWXCmvORVOTWZ%IIRNid(UqJL$c`_*?twTIe*G*Y!il~P_{oygZl4dXT< zGspe|DLPG|u$fr`$@F+S?Yc}du`shXcXKh8scA0vp`qIKl>FsV^B zNPUs5*XlkP7n+hO@7bPlO(27fB=j{P`)8l1$%-Qar#Pj9$p~+!ULY0jj13y=Wm5O^ z(p-=?u~@n~`7e39ZAbEhoFy^{+EaWWiO#H#B{WQ#SAc2t^`{*0&_SK|3;8+y0K5M{ zFzn=KEcRobqil)ASh2~<@R#U7GRP(=dcb)MPizjyrHMLm95^8JNr=lT_>tz#QUF(UmxCo zL85&VtYWr2kG8A6G$%3<1i_kSh6jQMUy z^Ez~$(lO`gzm3k*-mMcXK7aC!8q}z&DJ))aS+a4&R(Y1JM6vE>8QrVm?+h1i)WepG z9H}Q$qhw#co;T2T6H6<0rC_h3W(mTlN_DJ<`uV1Nqt$RQVO0YKs9kY;udf#kRvrJ8~3&hYT1h?d;qUzj+tmCqS#c` z^#cJYmPwt(*t`3EyoDon#o&{tG5>*xa_#1rd}YTx>JGd5?o9Gr^W%A|rTtywZD&;m zzI#$!nQY~ob;B%j^k_vgxefQ61$Zum?JN9f42Fk0To^Yc|B)TM<{gPaK1_4>FP_Z;Ax>{M7mXi878x~?;gss z`n??}hUlgy$)FrskIN*#6V&FIx|9-6x(!qdJji>gMu7jbiY3axoHe3e63?PuZpGB~ zioNM1PoSt)0aF_8)b8QVp}#z;0t95eeq1rGQZ~kh#kQd9IExr9c^map+VQ5BTx8~X zIcnb&)<9Bi@?sgKw9tvaul1JjrTa<+>q!PZ#y+mVJpIWTL7|`zf5GuRJPGzG>l4PP8agVNWw0v(TO=ZNhMIk0E%KSPzK6>@!!9mAd-L;mC7Awb z==jcZTf;2PZmDlCUHG=q=~Qcm-*R{jg!~2F{b=N-PX|Qg$SS~o7PQoVOu}#DKx`P( zqxbDvDao?21YtkU;hOxtf5Ke#=Wov60K2{nI|!bj*yxAoI2@)5`k=Yxi5|CjWopde zdKE-EZBSCJpVLGU&D3v;9E$}mZDAotgIPTOiul!#|H0f8_<1~xBQ#?wAW3U#k@a%+mK2rj=?p5;Lb6dC363ymZjp>9h)8e>@nP< z;_es)8!SWUwBwqBxVKyI;6zZmWThw(-H)1iM8`wGG-Fu$f=6Dpk4#5f^L^o7GVApa zwTNR+)EfWRbY7XlYP!`cI<@1%m7xFsrg=O)9Q?RywJX8RKJP2^Kpj*8DNgXlHOa?8 zihu#yx{*COx#^y?sgg-|Jk%sI6UNu^Bt<~(w{W^)>JdDFJeFVyp|QFfV*n)Q*+}k-|zpuqcZ? zB4h@Iv%UzXiuO1l*FVutxMploD<~7E^ID!PD*@M2(o0Z8>gzY1o!sVn<_QmIa-4v=(Sk!;m=IL`LnaX#o+;X00aWiEt9&kuiJpPX*T78Jmomc^5>lX(UHA&omsQl8M% zsj)vGc%GAkdreJDgTJH56WDNY{^E9~<rz9pnnitI4MJbI)nRuULQLjE)NfhiXaN zSxQM==iAl@$&1ipz{91u0_U`>x|UM;g;k*T>!Q(Q-645cE;eH&;ot{~hjYyrA>RdM zD>*{N)kHWy~t>0iq@DOhLs6?Qh8 z%`C4hDPhg)T5rB({f$wOk`0q80L9?#peB>|)!DrWM95{^O3Yfm4h>|&{mvDXZNJrV zs(`Mf@ID7n>V8Rj#9Le~$_+>;xyF+(p<>mx$BUEqg`v)3CG4KBzu(6y$wXkGqntg- zxL87uSK(ON?|f1j<~1vZhE8s$o}~9rhLLFZ{EbTr{yqc!nmR~UzzVN10kO8aZB`*1%G7NfKCZr5u_?pXnR-^~JJI+0p#-FNYx zH$VLoz7)kd>3uZ7!{1TxRA5QXAj%J5j7=~_{N_g80}9gQYnP>EI99w7$+6CiN82Rh zW`%`of!UVZugQGk$vrJ6T1Y?q_ZHQbM>zN+s>6_ZlXL6yq=oV0{Aw#n9IxO%tGcf~ z#yo6W-g#$Nn$gHv*d`yta&?J^O4OBCG1FYP`W5^Q_0NI*1dRU+GG!c*kV@~vadq+V z?)i&!!>EOtxh=$=;z~Xa&x))(mXmp8li1V8R=&70=W{c+n!>r=moh*7Ex09l*TS~p zyg5T>&qfM#E9eWy2yFw~6PP5*8Pd++hA*hJK03t7EdzX%d0ZIWk8aYc39{{kJsO^+ z0Ir8USgAyeW=RJde1{XVJl3*PBhoSb(>?DRVwGp~ww6aXi9qh-Tj2ZXrLnJUGi9 zmd^9dkIqwE7RM6)+&`43DPa!Z4Th!8Z>5KKp4bh+{7~C%2eT<$CXwxE1;iAB1B-1> zo6eCV3Ch}6jyL#z%)^LZ&>BXkYvV4O0TT(K{z_ugTMzWQE}ZIR<#DWxA&PnM zTC%W*Vj-Cx3RYf;c)E|c_Nup8cGSO(&z8_Cmsp{a1vGM7v0}uul(SCv{d?{H zk8<7(JI>0ET%w(0l7+)X)n$wG<#gsFgs*!F$5f_CK=CVIA-zoU=xAt)h8<1da+QyZ z1O5r*XQf}tpbIUz0feQ!wL4b$=jq#k01E;u!&-` zXk%h@MR};-vqGNmdi6h2GQrTnIfk*f{HWHO%y4A4u_1*^@cxagk{-Ly9;5#u2PVxc zUoclAT0S^eRQq{&tK<@Ieq>e)LOAFu6ul1w5DUy_0tU2wl_bibnke1XizOb!`!UIh z6GN?!lx!v2#Hk?~<1el_2Y2CVKAd@BZ9j9f`E4sMVL%oqg*0I0p!A#r;hNoZjp0wG z&hB)>5_nqGm)%c@{c&s_cpRnE6IPbHdFd6TSkASao^rUVc3-k!(hFO9)VNpfLSVV# zub!r`fkguLkbP94(rGaNs+3sK72t-%&qSrM1UO{>U@Cm6)G=Y1Yre8~JNGYUPyTd> z{#B80*?ZDmDf83FxA`&#D60OQ(c_~=vFfWc2X1+};ky|XXJeQjPttunoL!~w6@8x4 zHdDhkkc&QZ>jln@f8;3Em09p=ds5Xz3X)mIPciuAF)7f4m9`rfu4<^dz4x_#H;)aj zU9|tQt~yiBf6xN%k%#IU!p~ zt{f4{BDd<~{fJ%W)sD=hI491h=gm#rF2=`dRWXTl zQw^FLr0L`mTjfTuV5gP1h%-A-Few5U6NNRXYZ}CF3eDDTvP@GAU^$iIc>$i;A2^qi zWrV-t?@R{HKsBi-*lb$Nyw$m0A22pN%i7PG`sw>OaIYDG*h0NDg$T4#q|#d!r1{c| zF{Z@ade#G}1_D~>9+`T-UnU>S4W5D2JrxpH)JV>)D-i5G_W%u4cw(MjjI)Vsp zrcg0HTmngsJ5ruVul5Ds`xzzr`|kelqpkOqW#`+o-xo0U5I)F{$OCQOb%&AF8*eSd zSStM3HV(PTI77GnKK+H~v8T36qES3NKnnA|kd~pPQ@0LdG($kb6upsc6~HR9xl*Ah z>!~7y!t*n{omL6YycRQ7x-Xv&>ZX!AEB1@nE5|Ea*eYp+R1uNr^gSq&RS|7{ljhf3 zSd=y?K6kUx!|RrnAVRvW-8#^&d+Vs`k3^vUPf^HOd8J2uCcQoDk!!C==W_o6*)#O( z(E(%K7|71>Zl|E8h)mtBN~~#GlMtEIcb&LwOKNjRa$Ul(pV@cuR-Ic8r60@x12uC@ z(1-)wGlA8n>fgg8L-^=c55jfNGcO)r}fg=7|k7Qvq zFIUUA)Tktu3vl*(!=YO|aghIq3WX1c!4Y!^udfU8w|VBeQ?nVXSnII)Orj?85BPF} z!1n5|HrZa3X;ETG1h2nIarzru^chWMoI*fPRM6p>t<+V!E_-JUU54|WoN;=aRKGarEs@7CU%9A?L_$EeA z@akX5AH7P?@c0?giSa?a=iXZHvIx3El4Nyw3eEi^jL}XDrqo+m3)p?ImkEcZ@GbCK zcGk<+i%-=yvi;Hk>jwH%<&Z{nF#Eo4rv6PPcHaAsY2G~aR~32t^3uf?S18T`r&ZKQ zgYcn;r}5aZ6VW#pRsxQsfaGz)(xvco(Y@7r9pkLjUAq4h8b2e~qHJ#T#|5{V0%&Ba z%oM}$@GHuT=LM`>3pCiLy|rjoEO$D;&pc#aV5tZ^k6csa5vvmhg3WBLv!1R@G9NPL zDR5zz-t&<4=)s?-4!_-8*<0~cjS*ao-SxBOpX_=_@-{QgCb_;=G$6Eim`3hp9|b1q zuJXcuLuT^WRoL;fOAPuY#l7lod|Ga;=WXH>)xGF2#=(7kr)DIol>oq-gY5sgNUDYv zb7$nzuooE^j2*4tkmh0#BqS{T)=Bnmj;}!Z=>9&dnE6W`OmmtK*c0=dVN1@Ak9lq# zA|O`T7Kc(k>*B;EV<{2QAZG^N)lWE! zGRkT{qa0*G_$!799TLBjLjaN*YExl<%`xp!&%i&9cE9I{66M4wUyE!9#?N)15ECp5b6Hsc~=OR?!Fx9{z948hB%VzA>+-Ilub z;~Y^ea$arM`9zEIa(^HeGnM!dt_&EFXZEV@i0H*jl9R!qOhkzgV_j+wECW~N>9>qs z6G(#ATQBqqMA0dlYbsV=ZM#2b4|c-LE6nCaW5nFkct@1G*~;Qhkph2W)Y|+;9|y;TyxS`0)Fp5WH$zEoF81-S1yb7RX@@XC8Mw2763c1u{)<}k`-s{ zGhck5h`Px{utP(xq~18!(!OU{E>@#(^8-8R){^5@4CbEb(_T7N^*hRz03pbRiFgkL z2&kKLe_Fo&>O^icZ#^~~#%j(+)=II^;U=~|2X5%x_y}?y&6>Zh) zTL7Y*XsAbtfp!p%Oi2GW(bJan*^}CntLzjWL53SD#8oJ!*~&!quMP8@i|mL%SvvyH zSl}>XGlNOoD55%*l`AQ*4pw9SP6}Yb-}C{Zz5}fQ6|jIv>hWM)-IP%?FNn0uK#1q* zC!6kD#(N^mPtWO#@RvEmT(;;kOx&mR%N<`NN^Y&m=rvs8&ISB)uK!kt@8bDcu6q`J z6VmTHo_eTzRyZaoailcPUBwQx_ABEgTs!$|j<^%WaQLl4hqAyREd+0}$D}yvDQb)N?;_tvJy)z=~vsdk@f&(3*^{6vyo5M;W zO|pT?gJUj=L$r>wC{@wtJSaMni}fs~px2svOW}euMdAGoR*}UAwqlAF(I3UvWxVx6 z!l{?gXzMyPi_hOCzq%MyCvUmE2sq-bxix33w)r>CZJ6WAzD7r7p$r1Gj|K_hLfMX= z8hXl3Xusr@-c7?=6x9~ubq#zN1GJQHnWE?BL!`Dtk8~;Un6zn04f0hdUG9x4C}h>E zoTzNtxTbRELQU(aW%_n_{kRxOX8e?yJ>*Jj>xhTgQvFF6q3zKliuKrjY$X)s#~&{W zvecyEyowt1s!|t#ctpq@WK?Wi?Jv28-BCcF2|-A{oPFSH=bdB)PW9# z5IxSbbHKVOkAvb=<&~{ib6ye;6aD+Llsqgr5b_n;spTx%3@7r(E4EfdcZA=}zDRRV zbG@#JaPg2tLr<;yd=maI&QvXn~i1mzhl()F3Tp(OC@1K<`;(tO0gEwj%*P zua5IgoZ}E%Qf1NyiC;iqx4oYQGFKvCkE&FReP`(I$#(;$_N7N`1oSNt!xz{{A+LtY z!3CoBRm@MG!PO{YON@_8VfmKhn@kk+SF*KMig4blvtRUx;k`)4Bp3rW2+FbHl@Mcp zL&cHKkFS)MPBEXvy#j{lq#ZAKrQ8}YgN)^X#e6w}&WS2@O)TYl%UcLdQ=w78yM6xa zR|zyTH!-G|=cf|V*~Vg7c|l5Sc)YxVVSWhhT%5fa!Kg))cll<#-n*!s)+8mj(gRh( zcdP#yoKVE;Z2`IJJHj<;mz}Lq;)>x?1gpTNitG^%tmj1ysd@*^b6fS)bn_m;KuYZ! z_=hb?frbX@BIb$WCj+UQIK=F@8ryHLOezd*+l%HWs_3NFAvIiA!xkH#vrh+4ODmvR ztxX(0w-cnigx?jut`3b-qG)AE9>)exw*4rS4SxO3242&{995Xw&+<$J)4WZoTdR#} zxRckVQwCE%yW%YJp>mZyJeHL}?rSR$=jLU3Znjw(ygb!c^Yt{ReM?cDk1t_`lw=@1 z*|7+GL!M|lTH)r$?~Oii>+5atARnh#k;|;o`;=1!9i>Do`>wbgx!H64Qw~r-YwHDl z=)a4mxf*thhsCAdncO)|tKu!Bf`4VF>#2HG;@=kwh|ygrKcAJlf6ZXp7K5sEHh`+( zTgNj4IrLZlG`Y_A1D$)5Ys;y(*)2>cfI8|NhDJ_$!AJmp!{i4^?(gbvQ9yi(i@!BO zm>~yzGNAXgK1uFJMfJiP#dFL&AL(v%PB@FO;Zdyn675s1YTZs;J6exmefP`0$9Jdl zE3i)ruvHM8MOzLKi3n1Kr$`;Xf{i5ChH%({uBlTF`>o<6rs@Fz*3F|`sDARg4N}8c z*k1c9x%_rVkM~*YGNMC@Bdz?2`lJPNpcOz||Nye8Lnx$cbIW-0m(nd(53n~v=!d<>Rr{;G6) zZXz%Rtz`~J%@uUlVZR2o9mRsMU|(Jta+}+6(9Z?u0Z?X;FWE zEmQmYZnZ1I3D;PX@u-Z0*#p;pi+;x>cZNEPFxPX;E#STTp88}Hp7FP5a)x>|aksQ*<{5_zDU`yT14rqLS3saFV(V4H>FJb86K}XYhAD zUn=P$7^CzP7xpg7;^=6Tj=S{LM3gUu$NL95)#*;B1cA|fr`<_ppC}(gV}sm(nWx=6 z`n)GdBvRUzU|DUZBx;aYy*DhJtBSlIz!#F1rbZMG{cGX-Ierh?e1vKO!f);E-cAuM z1(>Zcjae|uT|+)oGn#m^awr-egpeH=zt@QA`LI3`4+k>IZ4EMyqVZJSEwM0Bt&hrA z(DkVv{|@Ymz;3M+a1J# zp^|;QDozbw-XXyD@@gtMy%(joXFQg~{BdofyTYF7#CzVD`U;PZ+N*-~vIp`r-F4ln zV;6v30szukH%AqxQnBdOOOAoAI4MZ3I-9hD<0)oB9(+3Bl35ej8yYSrg^PeB@WkRW zbn2g^IPs7S*xA!jR6a1kW)Pg#l3ag8;JvLMR6}gsnE+ztvHll8j6lncecAu9Y>YGB zr?Fi&fq`c#cuXeCWA3`jI7Rz%|Qy_tF1K+;?Smh)IR7>;%R^?cC~Qry$AGd^;%Dw zetR+-S;Gt^#&5l~kVdXO=??B+3aMEV(b?KVCCrh?!_9EqX<5>-Y}`+bejSkOgPlEOyn@j~4=Wnq==jEu37Sj(K{HutM!?1#k)~t_)HoZgB*gRicbi zJ(s|)^he{uK7O{AH1y;j_@mK|b568d4azZi>7oa%HR}9D?9$P5B*s|f8cBhX*N;MU z5A?-Lq@-S{DD1aU!dW$1h-67S4^ogxYQJVXJVDYsYMH(jo&PixW{H2V84$71v#1VzayN>El182o)?n} zv6(%58m`m`=#c)*cxRtp`^!xtMRtg;%<`FEPY50Cbu(4)M{D~$5mBec z4|MqF!*0pFbp&yE@>FDDUjWU@pr77YQ0O?Do4`4tA(KeoZe^*(`bOyG1TRdI_slH$ zPye)4Oyz%U0yygS=DPF{)Kr^d+Dn&BQF1;IMSF0uG12ScQGXU}7 z0#5SHMJb;swVVw(Jc7tuEhnKGz7FsK{Mx?^OmdEfj+Whl4}H3%M)x$aZ@y&x<~0)g6)Ae^{BVt7UtLRkYP7P6@sNcn-E|Jw6vm!2B?*Mu^&Ya2}%P^nrBSVJRX? z10awLe^xa@9d^1WeLZ&f7jeDZ7DJrG9(q_@fRgftD z$@b3q^Y+@eygwF*96xDnTG-4|TPzKEb|J^+;+%~_%v$Ruv6Cp%Vtmr}77U+olS$dA z|2%#rMbwoG?Rmz|!rbH728H&x{=&gP*RA%qQ%}Z)UTJ+l3u@GC`So;o6`?Q=%S_?K zlPi2$a;}SR{N#O6PKtbg(l)jx&-=d4mLrRS>FV@u=Gzl3|6$m*anLY!j-3ad1;i>< zzKKbFl=MrY(`y^!{*=P|Z6j%#R5~}wTXn9VMNY-5&1D|C@@-UpzD3eyCX+vSA2MJa zz#}kZc1peoe>dB7(rXpQ`)+^~qu>!lH~n;{A)XwyfTex!!tZ6Bw3W{mSND|IMKX~& z;8=_;h3u|vKFHHJcrN<5+Y@trV-VD!aP3Mes|%zrq%y}e%nm(IW6pvOB(Fif!~RhW zC|q?EUAs;PEyQ}gwn#o7CVF_pA|N6RVUfC)brja>!wJ3E&0k}s4=9AK(&yLyg9Yv# z4A5d5y1{meiwz@8{xSsAcXMpKuUBI(=%_+IPwL9Uh$6o}_Qlhkb~>Pv+y(_rICaz%$uwEs`;=mT$r1b@ zF-wirp_KdkT>v;XX;q$-g1&2AjBP~~&zu0wgJq->fliW=JJMYPR`XF#@WbECRcucx zKe;0v)0~{lUzodm0(x8G{R?_1PR_$T`%1iV^9OufEU5H|f7xGLFpE)A(0&ZyJ-{;X z1H~hAFq}p1(3}`+=EFryTJX)op2CHJW8GtdEqzRl0sy6?Q8Rf3g#hzrbzryO2lqk{ z&+-m<2XZ}Y9Y?f{h}y0I#Kv;sblbcLZw$4{&ts*tQSvKLs@9@E>7!nb6s$z~$O}avj|P=y*`U}x zFYsX_ky#2G*bmw?#4*z=SW)SrauGy`(Zx}~Yh7S;B&bHuyrEGppVXK4Ect6|uTis|I{(WUuIrHATBO|W>8c9eAluaMX zP|v9IA4nY-7ql8Q5GlBCuj_bmeyhILouYIKTus0fBYX#Fh%-zl?4&GZQ?mm-c%+MM zkXM!E+527d<;0lj?lKhhSi(qYko-8OfnDm8@Uvh`B8QB~hF(>XNyRD*KhFdPX%ujh6y@H2=kTMe67wD_g2Cc z&NX+{XJFbByc%4oj6hvum5?7|yjksMHdZ$)y%XEkLUyvbxW^s_yR8zVC7g3xNfs=s zF385^tj2-5U+gYrJzuSs>>WZ)n^X-Up_M_=DRpX*(TlX&Y9k z(vQGAPb)h1q-Iz^Y>s9>kNN|UdS}YkuvB(kd$bysojK|W!5=leEd4SpL9Tj!A(?OT zT7$BOK6sF^_ksSY`V*E-dSFlnxzSM!H?Fm0Ga%^inlMMk{YM{xl z9&Ul3$0-IfHcwbnD|o)E%~pURUsl^6H@7 zZSD*|K*Iyn+U*No;!gPlkK@LLXc`Y;c| z9n<_qT)cb7+V~nJ#v8|tK@X9Y(1L}~)5+jl*N023@6tfxi?f?;?O5alO&+9?HfM+Y&gy|pVo%CQvlqVhR4XB=9-cAxfL3lZ*yPztC={NcBQAMfFB*sQO=u%a3xP?=* z!~<_fsj^7fwtRkNwxoxXpLrs9A~4=3;x^Z(8aoc*ta80cB*JzL;^7CeGIh__k^OU3 z^kxdbeomMQjc2~cQP!b|aS z=(^0s+N+Y_k~6PfJ%Fq?YQUPBIOmwNggAK+#+y27cTu1eFhft4fi*dzJCm0G*XT(O zDz6AyiC6*2HCYgKJCtv=i0V)vH}pDpYpCU=Z1m}^w05HRR2C?1wVtQm81@I+A|@)w z%ZnXAjK(JO9~{s;XDzhqKNnFTL&5~zG1Z_0ic2*qEPzU>c3iVZW2vuu2|advNKbuY;!h%4V`@R|V)lGStp<}F?45oU4Jxne<<@-V z={TOKe#fKa^eB5s)^x16XEFs_`V8P7bG?!B&0c)<%$-X z5{31TX2WBDy?#z*a9S+?AE+Gzop0?}=#`EM%iFer)X=`h9u#i;!D<$QB?s7Ap4cr~ zC~qvwgcOrawAv)Tg+3i$F+g7V$2MPCqy6I5iWpBX%8s5q?cIB_>+>_C_fL7Ad3_un>kk<}UM7sF5fF7KG%p;dHge{&kXVMM4tR8x=-?y;3f)W{^ zN?C0reL^6LsA9yevAo8-Dwv%+dGp3^P~%X3x8<|8Wt0@d*zgJDg2}mSA=lxp;G+DS z4r>r%Y;NSkLiqqYeV_#Oan9-A1Cn33t!^=CMw3=I2Ihg1(}gQ;Z+7+ecw;?L1uQ0W zrrK29bbXdvdr8=EqMzqa^uM!(=A3mSN|bVvHHF>w`0$c9ZuT4@svj5DRr@o*h)s^u zeW3T|N+Kh^@f88Rf0|w!6F&Rb60_{zPJ6g^tsD}p3ui&kP2zcWUbzv38lC`kZ`b3T zYhEe8Y(9Vg_oF7EbnmmxUbMqHBJkYo$(-K4iQ0Hu>R3G985VyLSK`OX8h@r0YsdbQ zUKd%yi>vD8xOcB&Vp88HqIONSLgX^{O$i(9aHqCh#ptq)RE$S!)y<8NLyuoF{5D;t zGLU3i0N;VN;ACrRq7Ny2W|pbPyW+S(-QyNPj0+2XjoRN_-Y3Ke{QbC9MMt-WI&T7LL2GRNlDp zXw@7yH1=lFkt`cO>^C+1ef3Ho_F4nXQNaP4%=7!?v*e!Ei@q|)l;&} z^Jpi6d5ir{--B){%yXAHpbjUPYUh*9&W;xHd|!Pa1gj**=Ie;I@UJmF6DK@UuS!5P z?~Kzc`Q><`?~m{U_nEbLcbEFB_#B~^dhim{fS3=s?A`%tIl;0O3ly%kDNm&wvmU55 z%EwOY`07GdjydMYWRz{j+)EFwtA^kA%n?3K*1diu`M|v+pj|lkmEd&cKmR7 zGGngPX7-Eq7LLK^b*RhAJ^vuh0P=1&#joWkG>@9=ig`POONC2Jk-&C)G=?2S!xfFsPpyN z!2K7YyVQ5Auu1GhJuiE-K}^vS1ILFXQaG!jKo9#)ia5BIRMWb&KQ98<7NY-1LHnRe2sx1;tO2W<@6|#AQ>5QJ;R=QV-$N|@-d%8^ zxp#=OU44an-p&D_FKbtVk-faE?^oDJLy?N-eN}lENlvgma(i@XBe>I?%B#R)>Pfs^ zr#YuwuMAv>r%XSgfriV)hl@IA37)yU+mTA(1;T*~0uNvT9CN{KX1RTwV4PU~kZ-N0 z&y0-GUXHFt+_#j^sz9(BQ!@&AhGG{|5HBpxNs**=B$~M)9(d35hiy&NZCjCW&5^YJ zc_id4VfTds(1*QmNP-y}Y;Q5dlEC4JU7k)0x5c+e*vA~mX^}i@!00DEzr-y3!ocAeTm1J0~00}KOO$SST;5$(hZFDvUJ(jPK_hp-{d6sfu3>bvD^W0 zh2QaDFAZe}d(P|c9?c#1%R|fX-)T%XY+aTGMcIJ_#$wg~;;P!^NUgHhxsBbMp6O@X z{9a|}9VGTo=R)*nGc8=add&Hm<0CM!C%5{rQ`MAFa6b9K;w!RsL5;VsYQCL=;X*qS zO1Bv2kX=im_ZL&Wy6Yl8GmJv+{%Er~ZFqND2Vv*aJ@}O?n=*z%yE0%UEUj%rSavpl zb@0!N^jDBWQO|y`-iZRocAlmtspdQwGE;Cbu$GnVR&(49**9be2c%jt@>!f1Bb*bEsE-jS3e6OLanpxMif;#UCP ze2u8~7n*V^mn=pEf6j2j#&aOIPVq!8PPFMY-*y`Rkk^-3mmRz51vDICnkoA{JUWa> z-WziOXX-;L8}iamu0$TS>qwrM|J9gZwlO<$M5#F|T|pfhpPstK_jj*%YY4{!IQ|Lq}v1L)8J?CTeHtF4F+{4@g8N9zDn5w;Vdq?!Xj>@lJHIZ2;<@-x)7`CqAmIPdqfx=(v4$-{euh$Pg4&I}`sxj}6&u4$ zj9D1T)m}ESN{*x(JWuF8y(o531>I`Mt?S{iEmT@Pm6vwa*UzN)bK-!P0}2j1b2R}r zGP_||iMYl$mvi+ANjkui5nrxuGC8(*jRj9vkL^DuANdcYqoRpFrd)Z%;A_7E=QoPY zl}|9eFYs3Bj>frc_PhbW(f#VbWg(-J%`b)2NBcz}ePE#TN3-S3jql1{Y2kzsLsiQb zHza}MvVCPj4=qlqxo;POeJjrX#;@)JZ{UHS99dv|o-ORk5wAd4;*9_@-Cn3V z#T@s!F_Mjnm0m@Ivg*7i%x0PAa(wuq9S+SakOy(qng$1~5Tg7OXYW&g_xMu5)a=FL$)(VzU zaai|=`Pc(2N(ZKy_x@W({+hgN8$>!Uh_JNy6&80*_``hBL^Do`D-qio4<#J4ZLW6V&D05(>&X_y*V8Jly)h>s_qFwLdLTD7}onh@Una|(FN zwo3cX;=2RDSG`2$Qm0usWOY5IGQ8?k4!~Wx6Lwu`#Ubl0p&4Vqtsohu=VF)JEqb?h zRevFd@}2lW470cJGAVLzRFw163udd7opLw4u}}Y?E>bUiSBnegl*ov1C7C?LDweQP z8wDeE(`IGbW7ch!OQ1^9jH2at*xFBU2jvPxkeB@UQwDL%pU8&sH@!_#s1!!r+)pTC zeSaRxQ;E~qu|GZk2Yz;639AvVIe}#O!B4)!d8d~^y9(K#YFXkw2~q_;;){$0>!!7s zVFXw!@N%$*7w~HdtC$1b;O~!vdXfK1?2||;Q!%GyzJdnAb`SGFFo~pD1>`%VmWRqh zJy@~o#r6xd;rSttGh?~`l5%u}ysU>dj~Th=lhX@o19_^AW5x$u6WjO}8op1#afrDo zSz?3V>5>n4;#U#=> zJ(FbHpq++^wR09kyOLj08BqsGJmTpa0wS{Jr|7@K#;CD=j2MHO+xJGUt#NF+3RW4^ z6SC`b^_{e{;hU}-Bcx-CO1B+ELB1{GfIpui}yB?%+1$JQ3>D;W@m54-{|Bh3@(^gmGl{#^#m^X5elb>ERXQi4Zm?>xBa z68q!To`8>es+>_L6I-&Mu}dl0;x_K4i!I}b=xuh~Q4-Cq#$7UE&v@o1+(2^ff$UJ%n`YhRaJjTV$xYma+wFMo#GSO`1iIBPeF9P+{WqY3In@4+_EXj8p= zBh4SZSgHYkR5xtL^^gYSwX%>CWLk;fUTg}6UbVJOj$-ls1wkqbYBM3<7OvLs&%S=joHrRjBWn-N2}e<0J;10Dtb~|J)Y}!~m@Cu@F_O4nnS19y(~3`>{dMP* z`Ww$BF$Vhe=`NN&UjQ!1uukd;rA`$fUYO-9xVK1ga5M(b?cEx`_gnkK3Lnfd0T{m| z*?+h=E8edyDcVH++0Kpg{L&upL^6Nng zk{l-_dChs`hMKhNJbvrjv6+WY9uf-f$dmS>Is9CMMbk)|KgKF5 z4KZt3k?H@Vh`Jv8S zlxE_;;d!q$m8)iM1?Wu-a*tH^Gm<@0hu&#_DiBRC@mKBLb7&uc!8!LfqilDW(nB}k zSo?DvZyasT5n2Arc`!N0ieC4_u0h@X?28K7PO5F}nXlv2k&R@t$+ug%-?T5R?&kQK zW%?qxp-aH4-yNrjrPKy^?d-$#c0I7CAW`%#+sN-iz9t}E;HXOHaDDUY`v!Zs{Oca3 zyBYT>^3?H6TEZ*b1iNpO^A%JqiGNK(pZ@mK1sCPEJ-k!F_76ZhfdK6716|yMj7~+`RLf${++!TTO0U! zTPAn$YedroMSL<0V0QeAbL6hmXWpg|!#FVz>CXPped(urK>J?ak zJgP<0>=HCz8Jt{gRhSJ)`48nK_I$WB&~;l~6ID&xX{^-aQgzO89OoF= zbsTuOSuS!b7Ouq^P}o*Z z8!X{H$$?ltuMF9+e|vvm%;d5DHjaUtKGPI|o^uCH;9M5#C;s-`^iVhXd?qpp4Be{Q{ zpm_B%Z$TBp&YK#zlB+Q`N%HN2v~pD`ymclX|Ic zKc$i%s}!}3M4D=Akp1h5I^HiI*CVGc24@Wr0dW1lpMxL!RdC35xDDj4!q3w`>>?k$SjKyI1=X- z)#K6Fbeh(OlMv~nc*n+Jk4;(Cv$AvloEcdrNLY^3Oqsd(DiQ_L?3I0Dq~uWQBt81j zBZrRiwAi+`U>JQ8@YGXRdw=@m&FPvV_+=1f|46)eZZJ2}#hGqD!m|O- znbN){Ofk(NW5AglH$gL1XnA56g@|X=rrNQfL)mmMDYv6(n6#-`({y#EjYO#Yj zi?k*ng<~Clm)^*Mxpu3T%$~>{^)r z8d=f6W`KI}&5o#4H4eHo)Uqd!|fl2 zpeQdsi2gmjgY5{YTuXcrAKqTGZ^su5?K(uY9FW)@fluk+j)t!$qON=no8Y`_{#Ma1 z4nF<-go*F`a-B&5Q80O+{_0(Y-1txr;HE|uE0rYJ>F5ugss+tAVfgGqP>;TrKIJiP zh)(-HO+j;|EgWr9oL(32&r1~pW;%uD*5tArr7AJdNYe;{POyi_{h0ziv8{7xoBgdI zx)T41j;ISERXB7xp^2eBMIg|IE#J8kEd5D_#u|H<@*2R%RQ()iV<7OL1$b29?0fhW z`cL|!VCQ1#|8Anuz5XUk0%6EOZ!m9++;_(jG(~FMu@v24C z4ni|4VjZJJ`6%}($@g^nqC^Q7x3a2tube_x;eU&?rrzO62GQ7{#D;t*eQ(X}-&)vn zY4PM>l_W3Mh=h`8sFaYL;>E$YCAG`5WO!!({w|k30r%$axSGP-DfffaafQo+T-4%6 zhb#Q=-gdm1L4RbQO)Q*rKp#daB&6knJce7l=6Btse>|8IUG@tH zlSkh{8Ms7ZDCl*s2;Hsr(ELjO0YETHD<-mvo?Ha)CzZ8gKvf+FLz!JBYuo{z#Vgfc$(g|jS!*6x4@8Yy z6m0>->%mWXaqFp#tzhO35VrPGZ55p`U`G@U3PHIp&a&m}{r4HGMDyWUzl(?LxUn^4 zN>R#cD3m-wx3eyC(8^p~sU?k#>EJ>uNfiHw$mgSS@JNXBqTNu+6NG4AYo)p;(kC5n zOo>&POXG7gi2D?-vj{+sS#YT+jqTnY&^Uk-ijhUw*`mMB#*BqmtrA{O%E}jm=HrGy zpVIXaYedP9Cz5RcW2KTj28ElaB@3#?9Yk-p>( zG>W`|0M4UP!kUaLFdvcvCuv%w&sj4aEU*pAC7Cq8wNAv5>IDX5f^r^h%QN|tZUYtG z5rfqFz{{ZykiZf4iFMB-&@hReLDiw9=OY(k6TATf$t3V`sRA0#&`9(4L8ZDemdwxB zO3nu_i(1=5R*A$$CvU8Hg|5(&Q=6z`lBY^|#zc3Rfq&>hCz_-AWD9%z)1V>rs~XdwC+aQ+iUK?0&`e z=&)gzQnm)xQc!O38p$v!GM736ozl21UU)WMPBO+@l-H@ZjE-1MC1%AgVY@^Vl?p(1 zl78Uns~b2w zGpcvI7dRWw2~4G$r%Krqk$U(&hBz7Rwn^egNx(jxaDWk1A(-Pe<{8U*TXG1(#Q%sY zDBgEQ_uqwLFEr7PiQ~sH%E1JI-X7I=dsbIvO4;c>Drq8MUuD_wN1?1SXVcZ!^#p_^ zUqvvMbGkXB-1C52SsYhK>d^PkAMJv1)>@wLC`jMi{7h*NGDbNCZJPZ3nLrSs>7N7T zwCHeo<=t%(ZD27JU-YH$Pn4c2+*h$HTA;t+KNNJ6f}g3`JVDs%MT{8>u82uwUcS4+ z;vM5>gqD)?pc4Mm9QyqX%PhYT!G+>_&YcO=Sw@%Qcu5_t zvA61t>=XClv(0#8r;XT+hCkTo!a9k(w%U@pW7mLG97mT^S*+D-Pt^hY&(76n;9jCS z)~e?Ep5!E{p6?7?6oBl4PX72x3}w4}Tp{FO(bPP5DfMr_3z_Ar*v-X@L6xogWzh-p ze_bxLzY>leO`U1eN9DFMrVyPNTY(shSk& zSWlKKyQe$BH--CrSmii`cN(&`OrvP1Brw(Xkrmc6d=?5m)i#FNXA&H!UEi+CM#3hB zsQ2V1wv3U*LX$zbQjWb#g8t_OVM;d&s?FSIIPqE$i`Ke3uf*k5dv=oVrm>yQxN$D4 z`A)Wq_TnN+1t#}(YVxY-XmI?_EHqQ#_SSbxG*QeTe_h2xnq5$}6Ak`X13GgzX#2P5 zEFNieIuGe0*uE0IpB#CA+wKHP2ZFD1YjwA} zYRX@^H}W_N0cKjC-O4pN6|BYlL^p$CKao-(gN4gAw;U~O8f{8l^S9!#y`&W(G&wDQWGV+wIHYyZ0^7hP z>eo0bqvDf4KKL%ngjPX&;xVZ)GY!pF1^VPe*Wlz*bHjI9Wt-0;pKKbTUzpeyt*5bU z!OVs?D>6+S+yx(q$%mD?FPE!+kupp2-9#J%x)^80{ z$}jz~B#1-A1#MNXB5H3jbwk659zoZMnbyQn9$=UpHv$ z{)mSyVk*L#er|}9-n=*b7$#EFyVwx9{qO)i|E@zE3-)dbxS6%Qrr+SH+CBpriC^L z(Ve}c#|6H%-kq&;Ky8eDTHxLiV;~ z&Fr{~|B{;r{&XDa;f;J+jg>+-4@@oMO?42$HkS8#jo+f#n?q`!?0N0!#0&5v212JW#A@ASBG#m_Q2OPXQVF@d?9!F8F|E4bY#&`EX@sIEGL zNw>&VgEV8b59~pJHt@58TmR#H2;cp|HaLNKmcw}`Zo<|}N*;LzPN0y~RVJJvu#kFZ z4o&6fiz!hayjcX(&VKyVWR)}!4~@+7_+=evTklXzR|K1#jQ3G6Wp@A>B_E!1nlXF^bIWnU7xvQgsE zjrUx>=6ts^UA@zyHU?jAd}Ea=%kOr0PRSkNldL6?jx=qu-KQ$-;C%8adyW|fDGL;! zvxCL%Nh--H=43MQ{jAkOhP`f&?8*!x9!xx8zb~ik?#ObR*w~IRmqx;VXB4;hHeH<%^)flO^(fc;0fEl^f7|%;8 z414OQs`$p0ldykT8c?!QSO+XmFd?gb8l|=Y>R@(cdM&70!o%xXPuXmR%Y5=>04h}# zt|0%2O$9*;Ngfl%6UW#YwhaYt&JP`w`#J9pwe7LkKw(r1I2&y;FwOr#LHM_iRU77L zwc68(D+paO$tn>1<#ai@oAs6;0h+4wO3jyRd-k7((YxKqj{wriWQx(_P7)Z;>a0in zqd;lZa-)TA$UsJh6g7zU90*cT~&)R!l^A?jRA%UqE!>5aw z^GrtBY69QgB;5*xrb&L#j z!zl=a?9-A!ynecTC7mp7kC?RO-sx6>&W77JS}&zqEAh)sG~e8-_ly1pj-w_o!9jZs z`}148Ov-jPS6>i^^SM!InMEZ10`t~L#E!V|rEzjaclS z7poYgDZir(TBAUm1D9-chP}h5^%V5^uSI9k+5D2&$ zkPa@d2L26#E;BEPdv5vc4aFIQPNK&Gbgp$gs63DLhTrUH3coCKbNzlAG2;1rPN8G! zOD--0{-Y!^gwX|0?tCJj?Ne$`^cLj5;Q@B^I#WYGm%*Jb)<3c3_xeuHj9wE9R=2Yp z1tWpaQ{cD1Bl^-dJR5-r=WZ$J5%Yay(6GNBg!H|02R32AL8~ElTHrD2S!Y(F zCDY#e;!OqPVjr3a`g)Em-}?R{t&Wo;vvtL$0o$Qc%7C{ZJKd!?=vU(HUOuD8hVVDO zhqeNAT4q?_bnB#>z27 zeEN92NFP7;^0!3mp;)^Eju!;>D;5e~hzCi%7s*BbZBWbqP&Pm4RN(&KhplIwBzsHp z?>MWNRZ(pK1vw|>mFFlNFSg;?*>*Yp(az8 z0+)g-+c0SZ-Gf*`X-n+sMa#=SXb$xzCW>v!^#Z?`L`1PIf6f;OD`Aop{fA=xxg>PB z9k$~W+9FxH8psMHDptpLs$0b+lW?@4ob&R^y-A8Eo}mM}`m1DZnNKOF3_g6;C|8)j zo&;2Rs!zdRV~r-U|3jJOyc+A_@^PyZ1F{^MEd7x|K2;#eoOpJfu1#yn4O8J?<_&~Z zv$ZSHU3L7Lg4_7OMR&jeDTiTs%8Cs!!DBJp!Zgak+TR0GEgF*+^jUkozuu4J^OZAC z5Brp5#E!P?Ll&ASwtb-iW`K0=~`b4`t$O5%QP%Rcs4lhkovmNtPTtdhC?#+C@JyCd$5R`f=L}Wxi3;`) zO=(*^RAYAEF$-?*Nqowk6?P?E?;mJ-Z)~5vOKJNedYUxg#7<1%jJJY-+DW3MjiEV7 zt4#OM5Mj`QINODZ4*~)dOnbRF`B(bLL(LjrFDxaIRdYK>f37*v$_{H_<{Jt-#tGB$ zxv^*Y(c0K+X*>HW%G!@5y`(?e8DvRhLB_z~~EiP$2~Wxmy?r;qE5cqwWG z-0OdC4^OFV%{y$lwm06D&e#TzR6wG5W78|_+twbYG662gIEqBD32f7hXje~``q$H^+;s48y>1(-xn4-zR^0vaIbI0*eWxPL}{HXs+u_qSY6Pg?mgg>CjP zhpcM<<49mA`bAlOME3U`?qraS3DHCbYcg%r7G#oo6lTykP&VOMx4m@gUo+ zGHV>R_mqAD-;1CC!FNwWI0nuic{FEML8j2f#1w6p^vTd6Z$m0adqiH2y1#l2b5zFfS3a_s)v)cp1hD{)s5q zFM0LOR%k3S8>gyghl=P{n|89>963W5J+4!X&$>-EwBq}3K%;qkc(lX%@0)-3q0{Mt zg!+mAU80-swVS}>0l_ySft}%#@6aC<5+E^F^Kk(0r`1Df&|E)kn@;#VAsY{ykAWSHkirWkr0rr35R+LM+@BIX_SOlG}q`F zl0=~>Bc4+5@3VZOUSWJMzJJMfuQ$vS+k-1x!E}oPsu9v?nfVhW7G(wcl!>YwrIoj`9L#@aqtI8!|GFmfH2TKl z!X1`Bpd?x2FpN)%&mjw2W54#o2P*YSUY=Dq6>7XS-+|0~H8;COCJer6<2HoPmnqt$ zrZln&-Kd67ZZB75CJyO{4nTH*k&}h>FX7wj7J8aBq?GTI2Ir^>s&fq!kh6bp^~fny zVbs5Fj74<F97y^Yx5R_m-{;y`SwxLU`N*tE8K5J>nr%NC4~OOg=sdGapE%d#A<&jXwMOkY&1t=(1ayW7a>h7j({Pr*qb>JL zedHwh?jbse?;d)=|5(xF7X8V}ab$gZ1KuooIO*{bYlO}?E5{P@?H>m)keb5ZUn=R*6*$wcr_%%-9Ep+db(*-4x@h!d?O3;FPMtUp zp$xtP<$*E0FP z`>gFjRlQy(PO+ArC(JOhSxo^QmEjJi$Ganq9&oO8Q9}On$$u#Aj~94$=X9m#h`9hlk7p-dH9`#`k04p0-;3Ei>m4`F#QQX_u$9Dls4nw~DYa z`TN!~MgBx+uVQi@zB8P_U%VZHApcI=O9iR%pVr*ga}_Aa**-YQ6=ndJN!6NRnip;K_rZu` zHDYp%%wJOB8-FVt1dG@xK5L|D_p2qEDA&`KzUcvnsRrH20cJrWqhg-PCOwW{`T-+; zR^-z$aRs<^!Q-?s?&1=8`iJbmU&e?D2ZRbi(jI6wamiRs0e}jp@S!W7#`w_)Q|8o#tV9d zSdZ%6eWr9y{rDiebTPPg%XF-D^h-i&G){bUGd`RME*MwrB0a9EV#_^(^L*gqIPkZ* zo*}mRJT#WMr{xLhSh(?_j-DX-DB@m^HLqMJw(v@y*2V-$=?6_#;De(+i#*wv*)koV zyHmq5X9LB{*RMqY{pA}FQ~W$yCZ1+-S%H;B{x|_x$9{-i;({9su-m|AvvrQ{bgk&C z5Rf^HlB5G}V2;VRG%YZ{CS4|sxg>GKa4WmK->I-I};`FKs9tPT7zE>Gs^x+<&# z)TEBURQw~iGCGgYgSS%*`qRu02Us82hrY<%BN%THC)57)6aY_2bT}`H$6Ct=tcEZo;?uz#` zzzyw0JUV2?Vc!oeT$NLQHI757Jf8zESFtadas@my<-s=dA#sZSkpu1!1^6Uk<)vvx zJ`d7?tz|!SK2wahf&9r1DDlE9qoVEx!ArwsPGK5mEFTx{AcbFLH$7oC!hIUwxD(3n zFyaF+6?TPaGrVBeQB~ki&9wd}0ho#lb)m?fqP2K?R17f?r{}2-f*|4?3o$zjUH(Rk zlk$YAz*{1IAPRHjacfsBcB>@pnSrH=v%#w^o|3Z1!C$ zA4gUdfR0*a<#N&`0c2y+n+F-Z_)l-rL$nQ^$|euk>sv)fYt0g*9p1MT*05)tdvC;A zO(oz~syHA7r1xe=oUh?h29%DC8Z0owET8A-hS zQF={p_s1~CkA`pXJzy{;@}9<s)8v~;x+PdeR07natSTXgO=jC~y=2=$Qmw_3jqgu4?C*Mk*RrNZH z4o`SD6DjxucgWs~33p6wDx!trgTaiY$;grM492vQjhmK#H2j;{x8E9>vqa3 z>SNL+0>Ltkf1NRK0f5A8sB9kAjG#Wd-h*=&irnoOB1#p|R|}{$G!`@Lq&~WrHRYA= zkOkoJ70M;wXK6zna!YO5o^oan=~HYN6C-NeHZ z_ww(UfqYill&~-zL(yT!PxmI)U_`KZX(%cL}(OSCr2>3!lG_ubRCld z4k=Ex{@B1uISXVSscO3rKxbq0JjLn)X244D{S=7taygctfPMeBf=tWGvw4TmS_avj zzW4HFsXx`@-sL;^u8PQ0BgaFGgJ$Wh@T~l9L8VIq;zGk%JUSYj#a|1=R)G=(x~Z6F-_NFC}d;c3+Mqfm2P4vvpbpN&U7(sFesh ztGi^L|ItI<@V&0z-Dzn-e^~u(K_)dc!O(NVBpi7GsM$trf3g*B>6PA^;i(NRu*f}6#egbEk9ICJE1>iYfMEMu@CVpMasvm;*zyUx+UrTh=& zdqw~`nL^0U*2Mb88K>PMsxI0D4oc!xW$D(kw6_GwK_h%Q@~77JT7^L2OaYa&iQUu5 zoDrM-FYfu@L^>$XpJ7mE_C7AW@%8{#{tpmY&qsOCbeM(2B~!%>C+xaW%5zFzCb(+OgR%}Sf zgd2JyD)cz6%4-RHT-_+5a;nn)dG_L%7-3hfD&^(zP7tTY3`ju>QzPtyLhDWzYQJ${ z5Z|%>E8SE@HJK-kPnlf{b}8gxmWq-#rK5o|dgQg8yta7`yV!ZWxQHot%@jVX4pS8> zq1wdWY=DZi-5+&~jO=r>XvQj1AIB<}vpmjifx}2b@Ce5!G zWXG1TvsCmlRjyampQodW)@!W$1>=r>VMm6`Wmw1+3o*`{>3AB{p2;DBz-VicBGLcG1)3pypHDOyO(r{Gc4vsYP?1;35pmxagqC$c`~ z)RIIsj;`sWJwx|+VO#VUO-ftpQj%dHA=1DUl;4eSbZ?^*mqln(9}jB|mmo0D=tNPc z;brOYWDASmA;-uz^hIhbSyYyoCTR`>3AwD)LcWO$n4H(HRk{sRFWLS?@Iw#^2pY&ctG}$O0 z+j`nh1)C{b(Wg1E$zImT_$>PkYi=X#%IOHZOoWa$Pg#1`F-U7H2TS#1W6owK^p7yk z{Zk41A&k&-sfJId1>1_HPRgh& zUZ4)*!Am*vy@t@|6J&^_Z39E%h#lpcmSqN&xBwcBMUNTLgJMx?bS!4#lkD^Wfll)w zlQlxar<7R(VU9YI6o4u7mpcL{Lb|m~1;hOLAn`OS+V7kNhraH0g`-x5qqqN157E{T zI8}bA3TS0l)V-10s)+MtO<*h%j%bn88u#H|d7cC@<*0;euD@zGd_&{j#m{ub^EoO`|Nf-=;rC@gor<%^#D1ew-(`t=@jVI8(_WD$PbaB5>c{Og z?o!Uv=1gJ1#`F&{CUu}+#9C19tSXx=b>1^q-eqwnOwVMJkCP(4eh2fyU}puhPG`Wr zDc(+$dc`eWaP7tUS1cly0-Lp=iN|YB_Do5ONaA+#`0+@h!#GZ!Il*M)Z+saZn91b1 zc>Z3+N8|-V%>;Bog)tUZdIsfmS5|K%JJN5TFHKlBG<{F#zORf%f7Rz*Q>9wpQm-Bv zIll1ZAoe)!=@rP(l-J)Bjj&g_^Tag=W3tu{+(fz7WYN&H1=@-y7-MYz100``DJcPa z6Z630-X6{;;G~w}fonug5XjoL^d)otPJdfHPjrZ$XUq;yaSHbfE2)TpoR`IgrE zgp2oF(O6}lt5PwYsHOih4>=EvSje$)P=kM?=7GpjUjlH>&xD?NidVEzW&_s^+L z{VY$TYC$#nXsb^GhARAR4&J$nfin7jL6txe9>n8Lz@((J8S^963+7xX_g>`88Jn*9 z3(ed2E8{o|>BxaTE_?-44{UU1NRngA!k;_A^`WpXS4l>On?tQ72c%igfMuGjri5Vv zb_<2mVTw^M1whg1cdawi=V4GsbIYMfW4ajQyXbjn zW6}}|Y$AED?Nib+?7_67ZE0sBDUMb(a_viCso54<6^6 zS)F>|R6HwkUc)`=4N2Y_$Mm|RHXG17r?5~=e+4$t&Ev3TrC zy_6AE%+=A*M|$XoYGxz_H<|s3Q9SgW`bHb!$?1{C#=pWIvx{0moZc(W#b-TcgG7~J z`XSKT@Z`f9m(VE*q2y5AYOHPA8?C!|MXuGbftM@|E=r97HC_5=xrESaHS1_WoZv{N zG>qs$R#pQ>hl#Ty@?nKxRDGX0O_TCHID1WIvrQc3>u(^vooLSudx9%AaRC&vdC{Kl3C(RevRn zG6x}O21A=6)RGlcGEXbXt9eSh3Spi$B5JQtgQTRL5^r^lAz@2yi%jPzxt}v5bjk~5+Gp%W!~IwGLpCCzVf^J60Osst&QI4{3uIKDN&B^|-Q?}rcR z=HmJ{wYjefH7g;>4ZS&mHAt_s6zCBccwt)-!}}}VXIwLwHj{>F(ozu2JzEzLY!EOv zhM??;o_TaoDQQ(idMWU1F+8`EOQD#lJYovJV}IIl57}4`1Xr7;olSAPH}e(NZ9>)AVa#7lVSAqYXbb%*LY+n@B}cEIb7VM>SEOgE4PN* zF;SA#(g)fWURWkhrejpleO>K=Pfg`DQ{>jD#<4W=#(lD3V2k^g)OS@*DvZuU8|(Ww zA9xV;#Hm>hY074i4x7PXj&#t~6yIfOVRAcJvSTmK);Wpd$trw?d^avg(i9li)w==f zk!sZ!8K0vIGT5$~fN`^j0r>nSXd;`ixXVPw(?0VmZByy0e0#AH>WyuNR=fRgz$`+( zD@#CFG>C)NB_jC@_4&&O`{Ho@aJi<1a>@@W+ivb&t^Hcnsmh@s9cXXKYQM)JFt#w6 z1>3QS@m&E4>4X)`Sf3=j*Res*sm3^ppI6;j>@J7UQq-xDayx`!5ul862h$(Y{H47VhKX zbhRg93yP-PSss5&Bih%avIc^0XUfZ!g^_BhL*NN0!B^i$>VxorAX;HalSeP?Jz#_a{uV z$NIU+i}jlLOje3s zsLRM%B@^ER(bkFx&Yo{}_vS?N!AoR67n~BLlx`uTK6cb-$51A3Ei}MR_9L@BqxVM* zv^Qgn_@#ptrPOk%>OuMPgNcewOSZIW=jQNEGe4L_>nT-}RnjjpD_6!t%|j)0mJ5aG zSQDkx_w+s%GKjaFb|iWd9lih(bjFsN&(krWrxN|+RY7pAeIjI%&Wx<9;08-``tWi$ zZ((-vJWtmU9ly_F7q`Blf8cQW3JxP*%iLK(zZ#&>XTGOhYbj@2QYn5g=3}9JP9@5Q zvaL18DFFo{VRRb#`)N_qK?`}_q%>*nW8!qD(Xq|j13z1(i(iTe_HjbLrwH6buMSsE zi63{3exeLNvA-;4Pk*hq{qA`r_*uA1ZzTsm1aYXpEzrFh8GK{_n#fy0y$i>lF5RyC zAi-hye0>a8pR+sRXi2kubXPN~PTBmx4_$JBs;2K)0H6x(R0eia{QfQ@$N@&#x{j|Ey^=BQD%Ky(f7vg3wN|;JJp3+%xo5?jhItj&2Ok8n7Zj`| zup+utM9>$}N0rB~N}E^`5v16^Bqkp;lkXwZ8^%3ojge&!1wr6y`T3)&kaO5O1hVq< zlRXXVOWP~vIeTW4_+45T8JsIy3jN*OmF&pW01^*-oM5+Rq}4Kg~xZv`Wcc!RG5=>?$;MP2 zcS*`!VUa+k0ESj6=>)%$*YNWfW@n~=o8$wr3Mxx8O&bN`~&9tTrs^%aiy80$?$p@dV z`Z+#BbhQXNC4iVY|8~8q`wyi@2|STiTVskWvAQ89@{}uLA3U&@x0+NB`4*1zD_Y%wYHHXH)ga4vtSrB!0`!(>e@%n4W# z1?!{5gzvdtONUXdF?)>thjL1axZ27|Tb7ZqnkI&wi&1$?=xPrIhSgA5h`qJl_UJKOFzxEl~S3Ud@LN#C=SO3_mi6!K3Lr>mwXp9|G4 z{8TT!2}&sA_SX6G^dT?p#K%tIzWFG5IMRQ5jTrmEkX!<*#I`vHbX5VYI-qp-9LWRh zdias!wxcL<4N2@bUr#-a@|unImh-jl{BaR?VVQreVy~E?t>3cD>(@QT-Pv}U3SM9R zHsi^r>zNi#CmN3Bo2yt$9tk%-JnD?31nE1?O%Yh3pu&7B_Ed8r0@r(qE_Q5K{6({g zPeQuo({-NANEzX9Q5O#Y$6K@(wwaTBP^BGE4*JOe(!s=WYK2tvo=#3>S+Vr}Wdr1K zrv@$Dn1E^bW1#!s0`D4Z<`DU{DOubv-L){Rf4tJ4Q@q{EY^hEYVT}r+k<| zCRymR(E5HX)F+2fjRqGp7TX)DqLDNW!T(Sq$is*JL!o&OyZI+$FS%l@_!C8Y(Os)E z&C#uvO0$;j?w{s^q;~8#m~_n&@Ss{l0|ljC0vrH^{lsoh+qxeW^pK-zdYaYbgEsIG zq<*-iC=dlgYBos?;uT?=Yf#**{lQ@IT;p}mQT~Qg)H)RZm!Ws6CET^zF{N#g3XfDB&_|{8M(75$$RWFPN1R#WeL1nE{ zsVE*uSQ~0R!P;0L_o&@d92giAt;mf6btbVtw|&nP@$*%-~8`cR9OvkSDMoooM7z3afzB;k2}f*e)+rP$!5xv#mW6FoAA7(q4z9Z z;LaK&*dN@Q1Zb}~q3B`aP+w=aDDRWVcu9`6G-TV~4V4rPxM7!d2Q z#NKh#Ypk`oD~zElL3vUs&K@AHvi6aCM&m5*;t7MKB?(-i#Ty(*EZXdb!?_D|y!F9- z#Q67HP0qqfNTrn6zK3aEux|9;?!UE8`IJ_9rWp#D&-QQ)u6&XMkrR5ff5X|UXeo-% z)w~Nl-fv%?)ds^Ss?@9O^!6*5y0N`s(e)dstj8%3A{1q0zLzN|V~12Nqjl;a*z@7(*E8lej!ee7ZNN^897) z@R>XsM^>%v^+nh-#AA?QVz({=3b@Eq2io5fovR?DN0Ut!YKW74$W@-M2$c3s$MR^V zNn)}gJV!-Y%AeJ_yf#ayaL(B@=i}`oe$HMLQeSmXH)XE@u3x43ekE{4Fn z#37ol%y7Y7Wn-JN&dS26@%Ha;nSuz;$Zx5C!(jQD>NxAT<6@5eFvbbu&L4FK>FOx< z(EhvHE3P6%36P8!iYQUJ(hp|?Jd4iPsw=D{aT)4uPB53;%{*M}G^$AljFlqK!w)UczGku~fVvaM}^M!tVnjY>d+TV+Lz z-A6Z~a^>D`-`2@lD%VgYwsFBMk~P;srBF$&_gGJxgy42Ea-R_zd_piW2Z5Ukkq7p^ zE@$(q3piEZ!4~AUEnOx;^}haBC*tM+lYm4rF*&FHS-5|(jFLC7m3-H^>Y-mJz;Nb$ z@{Pdd2Z~>dKHtC!aYU`-SnGy^Y6TIc{G4e14M`Ew#k0TPj^0E`kIJ=j5n#L=CV;MO zl^Gut(ilNtb0AXu2;I?syFIk08RX4plffC7^E$Z_G6R z2b@7;zRSFyWfaD`XZcDw9_@-bN6p-Hvw+-|KEP8%h0&2CzrHlj2 zk^)HXTaU`9S2QBJ7Yl?S{vw*e)&6EsJ*z$|=m!YJRCfTH(2nt5Ki#qu?m4NmMPB5I z60-Gfd+wx^#}eU>=4zWl9_z(Q@T(!n&VRi}s1)B;kV<2>*ylJqNF)lJ%Wo`W z&&xY@#Z-Gcm|UA^l?m^(9R772%&4H<&e`su;C_{StDzTtOHxCCOjt$!>0sZbME5Y- zuo5}v`>RO|mT{Tl2h?jy16Q%@U`cS7<^S^tw5cJ#A6=2NpxnD6|k5a<|o~e?p(3%S?D@cS%HO2wq42F_b z1(=pt(MLPf9MnE#Fe!~AH{CM?AM2d7!F)K)i^gN2tx{^KHS`tP+@~0#7q=s2=#yBv6urgGs9^=-XfJPU0 z=?nP+)I{0Yh5g_?j~N22$g^A^igk&RM$o%=75q&>aMukRkF@=tCmVnae*sx(t6bY% zItgOEbA zc9$vu<)kYZ{(WiD-AQ|sJ=}#?m5r_oC?D_~VxzU5`sz&ESt^2lLhv(_{Hsm0Uld;>ypcmaZ0U>%86E!st!K*Gq|NeAZ*7s! z*{w$J#GL;CD%Be8){*5U%=(4=s+sbPX%Zi+3LnLqgtqc5IywQoG7`&>zuhMvG4=0? z$J#XSV^vGtw=ya$zFGyHOr#8Ck+}X?rE9BZllN9Ek&pRk>a2eS8LC%0rn7N;w-MWE z@jEG2PzfK7MlwIGDbxH%JS!u#aL2|BF#yLW)rb|co%Pt~_GvpOc3g`3H%8j7066*L zC{NP@nJvWcxnlB=WMGvnaZs5&fIR!QEs_{*T=B;kCYcoCQr|lscmU*nBDGd}qb96d zd0Wc?v4hkF>-ke2Xk}9wR%Ge5SN{O7LOSCjRgC`td4R|3nu!_|C1<{AbKf`z^q@rQ z@d84ExER__f2}pHHmLw}+h`c7%=W7zU`XrOC?C$0v}ys^6m|wqe=JZmB$G~{aPo28 z)B#jp9qpX3Vn2oRkIJn**a3oV#(2pUQ_Pj!hzc9NIsRsg8YuxW$G8_<0pI@suS_$b zOe+JBI8*%qr8ocvjXlE3pD_s~?tv`7jBQr%Da6;4kwKm%hZ{h|-^ zq(o&_r0P<&-n?>$!*JRC3{f4r4z`oEf$pO)L8nLdm^`t?~-LQeSG=<;nJl{`Zt45$(PFh5g8g5s$iK4C8n%60{{W3S zRucaJyADsOAXI@{KYnG$c*2@?6OjmVaIvbVzhBCnyS9%p+#bXZzo@AlW5($P<1eeR z1OEUW{{R|q?qglb$rwEYp1-bXST4X^5II&PdbU5ULic-0o=5$(2Op(8N?K2qA#OhN z4nI;VX1ekT4duk%fpUL9XbngV(#ybiaoKTBX)Yp7*or^)aE&-V@8Ty}JJZO0nh!=V_33T&Vs)noLgM1+|Gs zqc{Hms)Q?z3o#rlgr7zK025AZuF&R8kE)>XNvEu@ljSR@`hom~Qg`yf-**!GV1JD;$pep?@SpZkN&Zv;b%=f&dLORrepJnX zbV(&1j1@=bO+da&bbOGd{;T~-pbJeUv`P?Z(JY6nnSuU7oGukkQaC;G z*~jQArr8~K#~P{r_QO)3yW5S{>yGV!X(eKtxinKrbtcH-S3lj3CpBfX>!gTDC8VgH zrVsHoLHzPCZ$AhDAFgSB!#&Cp&vh()b0KB}>xxav`VKsuL=&t;nBlo*ISK#@Kb0t& z=1d>49G~KMC;HMN`9oltlOMtt{Ahiwb0y0$^evC%GgV?< z{0$yJUb2uq0;>N2Owovx@fnnl;WDxP1}GMTjl*(-#TPsTkD6{y`XG!`)v}_32x%gf zx#%Sc$Kh9^)a)(6jWj_koaDcNcrTfd{a!Dc?dqF4APW2x`Zdn_8_~u zo@0n?tnJr5NCec8BWwh|TewUQ%QYdmZ~<0SA5s1k(JJ%4XF})Xc*z;Fx&Q$4tWcTLb0lGG|$i7Elnmceq>O` z*EErk1L9IFHV(EkASQRWE_)M**L z6-@&iJjTxDW6yE_0N12w)qot4+#FD*M#%l58h6X%_eEYDV1b9v|tap!kJeD99 z?dksj>!oO%@)sm@=9{u({3U*v6qxQmY-D}!o7`rU#Sva*Ir;&LiP6}6(h>O@X2{L~ zW^bi5B5x`Y{{St2S}F~({{Xf58Yu;jdpGkj#~^zQe@c28rRuVg^fjtBso@77g$7-m zgdfhl+n$BHlor*Te8Bw;SV$5-y9g)LoKxEFL%;wEayIkxe>zthNPjc}+wW>gdn zSVL_aahCp7B3nkjznxZXMghQ=C)ibawOEs>(lwr7eaGgZWZ<@u15w`V*RhZCWJxL+9B>O$BIJ+a5U$pREQM9CD0( zvs6~}%l@t@54#M1I+Jv)K4Rn8Q?n-MnXO{a%CM)oq~21e)r1L;tGs@;0YxcX+7?W*GuM#tz41L!2CL;dFV$Xa4sJiM|X z{{Vqx(;7xB3BW#u#Y~bFhRNEyIjX%Gb!|}kX@+F)vhm&)90!gV_X`~xeARebC zkz?8OIp^2`OVq782#~`kDHk5BDU!i%zkQT@Qnlh|YLb6i7BeB;vv0d`PU0GtP`}I- z0QEGj_FG|}m7Cg$6;xxX_U52SzF=6Yk5*a*p)_}iDFbK&*cyD7P{1-nAEJ{^SrHcj z%X=uQ^3U^g42}3^p5uBIf9XX5(aapZic2Hsh)6!9 z=8{2mC*Apf3enjLj#Qu0n$p1-?fB7f~|pK2j5D1~4HF z>}l&9>%?mmW7&D8wcYHQ0u_yY&QIhizI^G_aEn*M; zx#T~PrOX_KXVa01KPvtis6N30H~Pt$y%d33&uuzl2vYE9=t8p*}??=EwsTX+F^!e)3tF`-H$5`jba3hOBKcm3bQwdX}aJ zQgG4+KGlN^8imCBi|tOqIynsxTAE^Y4acIWRj01wQO zT0iiDK{^AdYBx-MJnf_MU-7KJ_)N6>H0I@V?)j>BKM-Im`?oLt=*>APToQ@U$KZ_` z&9{q-h9BMY5A!v4D;*XbE5Z&tu_V`<+<4nloc`j?*mfm{@~rmpHoYqqTSm{I$)lCM z*)Nf7dj{PKA@Wgo{@dFX zJ80Tss6^AERrX$gGgr+LA3O3mF&Bt#@*!9h{c?XwPxwWAMR5NBzrRS-^(wf>=U$U- z6{Vs99A!tKKmBT1Za00{i68D0Nz{E1a=xtdqu{G@LfmT8NhffipX4g!9stqU62k6w z{{VQXuF%gfXqbLNkKVY+eZPijbFk!z$)|vHobhcUf4t(l3#LVcW3JUS(ckP;<-s@2u8@`fUS06h*Wj9SzY3`HDg z(6pJ);ZAK`rR9_n=mE#_rFk)>1ZYPHCk!_qP1&WAC12rRrfUl0T9pnWA$^u2#+Dsc z?a%LbZTE~X^`*+n=0>TMM)>(h=4r<29lK0`A7TDAL;G85{{X0WKKKHo{?=JIW!QZJ z$n43^Yd1)VaV4I1irQWGkwec>AV zo+_x-;DC`CdxyRgUG0)V+~&BM1hYY_BQ8U1NjE7n() zvFtykP=e-mn{!%_x$4Y6kQKFV=;{fL$LJ|HVv@eZNF*bewvSSCPd4b^cqu;lsokN* z(q;MsLPP+{T;ZSaQC1Q)#&$M6={o@5-8lJ)A3;nz0m1@3`qE5*HcJIPvTD1_ zFy=V{`cw1~bR#eTOUI!U$0j}mnEDLWbaF-=32$7}fM9dG@H`4u81!tn5-~sRk^HEg zod$RA_NT{j#?cuk{ek^yraj*@vH5@SE@^Bsyf;H9_hg^sX}Ua zAe|SPa(xtS6(WeyentRPsUl;L$Lqx=#*-;Z*9v~@YwpihE#6j883VW1ib+}%{{Src z_p0XO$x*SeeF4oist0pPozX_+0D35>T6owJ`A|EW%z3AqmYvXk0<1v{PPk#_p5?(m z&x%&C`GuM7*+~~ceUyyUF-x%GIQI_jTbi)@ZNB7isQ&*GOREcsW9IBceN{72T3StMoZd-r#PwwyepN|z6L-*_ z)=w~OHijTL%V1~q%}!y8E<=)#8b&OE|Tw*sokHKINQ2tV4+Pw7ZxH&^k*;0tDy z=cIozpiOrwhK|bZ4{WedKQo%Y43?oo2@ZXRDYEaB;u(+Cg&GE>Q9Z;%GflYv0C#ZW zv+piooRpH)WoGG>2m02mDTzwTB6IF>{VK!7ZLy04Q`oBJgyNVxvlhKj)eN z2kFgB*H>{kgh9G4=O;g?r^%?qjpd?EoZtXD{*^3|%Id9{qYc^HC-kQL#D+quh+U*b z&u2W;LiEEh3lmCDLI47YtZ^W7&d>I}cKrg|85j-cUXFbo{ED zX%Jeum98a~`tam_nXL=kb8NuKUDy4S(x^PvuG?DR#=d|Y(^s~Fer0*?Zrzkx+aVeM z0G6$c{#BqSmE#K}3AheMNgstW7DPYj@Vk-xWag&c(zYkK2zv0nKRRx}uheo(=-AsC z&OTb4Gvo;nE_oj?C-SOn*1+J*So-7&77$2qg!&e4Dr{BDGF-$v;Ez#RcNegE#x+U$ z3va5rWv~JaJSGp{|1%hr`y; zb#F3A!h*l;bOO77?Cn@WPWYAjghs&p@mM#ycZjYGk8d@ys&Tk6ax>2$RjyJsfAGgN z+RnXhw>GouRxmW+lNfMYBaAKyBk-se(_7VJMEh;~gg~N5qF|>U_~-f8L1CzkFu1$0 zmh$&`CUk=*c6($31#J%%Y6}xx{esybi3xKh#?!mman^?Ub>$jTmWoFjbnR3|9_Hs( zk|@f%7}2+GA1L7CwM}WP-D%!u*=%lFOztm#FbapDAP#y}yN?y$TePZ>+g*9HnE^K- zl>`i^01k&f)yc`GKA~|n+>WvNhb|IAp#B}}N^x~HYQ}YDsr9=z(eLkOl9+?UeLK|~%Xf<83t6>kRFj!!%y|T0;2wX*tJ&z$qpY!^kbenW5)a@rS{D}5 zf*$5AMpaw{c^K@^pv_vRXStNCHl4R4u~Vlw>Qsq$&soJiNV zIr3YO!BG@_2Nd`bByTKU$7#R#r$Wp9YK_cRk=%<|?o#t^4)lqL2OmsTyD8ZauHktN zc4-eQaObefh5mGnGOj^HLI->je_FI7hk#yH&+w_o>56bjN&BIf{qvvYTNvD=(#;~_ zUjxxtFBKQr;W8FGS^bc){#B~h@(>z3XvqHbj%a0^Mq_gXeYY0C$fVjsO8OK;3~`l_ zeE?ijjf{JDJHPv-@lqRtCi0f*2XWP7U~%|^M#tJrk}eoiPT)qrg?kYkt?5hc8}*$3&>jlq-sFp6QAfRM{hMo z_#$;5-VEQ)o}%?CNS7)Ax0}o7pEBSOeZF35Qh?{omK9(I?8zevkfQAwwvAMD#0H%W z)}Lxy&eJZXec8$%EeQN`Qr(JP^RWPEPg|%NJ(Y**Y4h6K!iw!Jg9jPMoLl*vXWE)B zgUs@zUuR7GgL1L_$fewYgoz9+`|+JdeSg!hUdKUz^VvM@wR zn32~9{{XL7JnPmOcM6~HWBH1V$t0%@B=3&Hr}C@lK^K)U>ux@+gZfex+TS!F{{Xoz zKR_yR1cc!&?pXTpKbWD_q8ogPH+LRsAIMN7S7_vrfVydv{{YiTT7zwNpnt@^+9U54 ze^P19waz@CThq-fe=1+HGA{43sn2-}0YL9^BWpP$FB-Q{PzA+0Lf|R8y?L-Q5vI@b zH1tWx&yj79_G8`r%`|zT@Q$gDpdnv`zZ;}>r7&DHo)O~Za>f(l*x7o z23vT?yO#b!n9=#We9P8gy#U?E(gq3`t> z0Uz0xF(Dsx3=glVs!3*FEi{qxdDy2P&ZRdssVUWFnrhhvUBcerpg0T%%703Xi)CQ0VM2fBt7r5TYRg1SpEJ1kp38O&vq=4b zQhz}}jwd`pBImoXU*||Jt)d_+BmV%`AaO|7mfWAS#h>pp5`QXW*qSzq7FSn+NbKvp ze!Nuj>l%#LQB4)>arsNNS{QN1(>(q)o;3);`5}n^0J=Z=^flCy9EggKVrnH$GTgY% zQ20wOUggZQ%_Ppr30r~Da{{TwGX*Y0E2LAwhOw?@>xcSl` zJ(Qe&wKC@DR5=&KOeAMKM+qGXQM`XTcJ>@$2eP0a%+x6?DmHoN1C6Q=^rp4SMz~2C zTRnDvlUIESYf{3yka6dJ2anKF?{SRAeyu1U&Z~(-jP1E*_0I?JqjB!ezQj-~6PY-{ zD!)#DP$@E)-R4L2aB)!$z0ODbOhq+}4a+oS`e!r+iwgXFw{Z{mBIos~F*Ji8YMJxL z+ybfwLHA?}{_4|M%H1zYuhh^6oHiTKyIvxdoc`?rc=tD#Z{J{tGs8W2^W{xrCxoh_zU_wvjQ8*5G45Qic z{Dm-i-`tu*>y5w8n~8D$q%VAu39B( ziO9)Pey0MS6K{BjX8J<5!nb^~(&<#rS4gZ*jiJd84%RDZIB(>E|25`WzWqHLZv z4EmCP#;as6GQpAY^DxigDbe|oHrUmep2#w4pkxC({-GA6j^uz zuBGE+sUjBpCC=PuI}a6D$lr9ls7FxL;V!4CyDWE?k{9`KtWUjFmrRn(mbtgP`MSG; zyN}fIQ5j19@uZ^*d%KnkzDX~isT2t%BchT1?NuIH@f>akxfG`?KKLHzCbTjvJm({G zs6Lx1e$oKoBOmrhHCyhka)-8f6h2vYvkZ3ug#cL-5`I6%7q)Mw0S@!ft;>s10P z3;QUosXoRSe9Ce8SDC5b%tUnubHT^ZieEZb8EG&**#7`ZvmK?naz{U%SyLJh z-NX2iK+NU1W&69>`iRK=YIyEsVmC1Z--@1TA;<5gRUU$X+l%4r;I2rIV`_{7oU!ow;e`Bhx%l>KA0Kc7Wx8 z{{Vqfi_`<{627JeQG=qMTDjlRI%39_-}R_~i2s%M0VwtxiD;mOFl=59LD(6(Q5) zQI?SudkU~1$lK-y`c+vkVOQE5GkOY^GVC`NL{D={%z6%Z5jrR**k+a}$vmI~+=_|s zpp5PFRgbZubqEhv1M#C!v2z7S%D{dV6p(qGyo_olx>*PA=!1QCA?FHx{1B^qsbki( z;lQ|r{S%+mQx>M&W6c0Ov7cNI{=Foy#;cv{S%X=%Blm9))tCy3U3Stn-7M(d(S|g( z3npARFY*!fDS`UbqF*d=BeQxUfAy;z_`*Cwu8EV$=(KLK3qy2a$j0w)91ky1vwrNGU+F30=M#~-c*4{cDFsniw< z22f}Hrl_81hs@F{k3|)LQ&Nb3rQX39{^}f`(x7IybNlO-KUX;XsEk|aHqp_?BodS4 zr;p6icAZ}Bw^$B1;Rf0Eri$M*S;=|haJKXloO z<)k}e5;yyDAO8SVGFa~fgZGoNo}xS`{Og}z5Nb`9wy+_478t5`+WxDtyxqy{s7+F( zA#=p;A-NXH3!rBI0H%xytnEiyX2>oPJrs)bOTQCcAt28oKJk%Q*E;Rw*#=n%`t7S! zlH8Y2^v3ZGt+R7BNbFnuDuwUF1dTSNqtggEuR2TSWAeorW8IrIp=`HPE<(UP_J#dT zUm_t-Ugt|6j@*)bxgYQ}={#pFy&_@$@x^3Cre9pLfp-cgaT4!8;ayIXq@}upBtBz* zd7@GNG@YU}+>UyG73u>%OcEdWTC(muPp(LR)p{;4M{gDLk^YQ|KfGAC^Q~3U?rs<6mQ~MXV86?nhSyeF)NDr3 z@g0IMxd2tz;amc-s{Jr3u<-nmk&CBa$8r5?X!L!9GU{^y=y<5mwSL6gf;T%y<4Gm- z3NU6xKkori9WKP44D&axDCs*GR+!$0tHo$I}-l@xpr_n?l`8W7E6r!oawvPt`7?=L)s-@>S`yqyK(#J zq;u$83hSm8sF*Y0p2Gyx14@oxon&ynyb5Y)P7*nE)07?GWr>fu$v=>%n@QCE>dMv} z{{R_G*F^eMM~}73^ix%vK!x&MpC8^W&+ApriR67ukL>M8kKNxz7(d;ONBGh%nX$Lt zYSSEc45~ksX`c+*7RsB3Z&sCv{Erm%@Y}0%bs~-jl1ZkjY=xdf-J31? zk&3TzFoZFRNRf~Cg3Gd!P9Enpaj zletl>TSPsue^bRzXQe@L&Bmv9cHWWBLjFoCuw4=n%#Mxd0@TszmhplXJP&RtbJIcc z9NTExYzw@q#sKRhGauB{ww94xz9YPLPwt|K$L2C?ppwcwe6dD;ywpEsE7A6kL=>-b zlCv2epKTBdkVl^EK_vbrtYR1!3IjGeD5M58TrqR*Y6me6Rb)Tzp*1!E&vPaY%XUBP zf+-`K?C1RzV;<}?QbY@55y%I)san=o`>w!!Dd-xFR*|`8P<=C+ab3i2lOOKU9kB;% z$j9&%H`rmuPndp<%eCO4wA9 z2SoY3PE9$^d1>~HblXwpNe9pJ4|7p%+TSVz@3fj$9m_E=PF6-gg-mwK%QIu^P^6L( zk1zUFh2PYKQSH`~85<}A3H+%5Z{nLCz*F5#(+Yq$rt*`%XGC7nU^L5f9+ zEx)^wh^8wjlO9-*`jMJ0J9j4YB2V>Tx%V{LWSx|4XvelHQz8ZCESz*uDujt~BjzP2 zJbuQXkapo_}el*hLfHd`Gy`r@no znL_2KD&E`*qc+k)nP-rC1I0TE<+SO>f`)s z)Hc3A{n?Ed&{gn2UQ!3C;`3`v>b+qcYn7)9O&go~TG{RFY|ia9jwHkA0+*RNKSQG`r`8 zW^YzbKb<>8i8h%%tHV6b>nw8Tjmkjh^Q$(IGC2op4D=u4Q7q6{N3_Bs1E$DF^rqWg zHR~`{k|pfK0iVFsrD4A_Q_F~tnh<{)r+C(}p!pX$`@jCGA5UoltamcAcg_I*RX5mD z5Crb6pYMP5sjCRcxq|7{9?~fIKXkDllT|jn*OGNqw_9W!7HI=#@)@q3?IDFj0Tw#^ zxva^ih>`&;0q6}DOH@82`$R$R7|7?S+(0##sMwh}k4%;JpT8WRH{n+m+GbRY zELrT`Y4T~8g+q--NXMdM`qTJcf_kk=8hx{%EhB1CuTS2P2j^M>T|Ekow-d_TEIrOSq3#nMX*I0mEoxx&FN+6O!#HxV%ecE)pcVPKoo~InGIj?OY z`OJt_2hEH(=}Fq!2Dd8QHMP9at30e*g1{UBPnqPE)oolA2RpI=r`+7z%V*}=M3QGD zZ_X;UjPqSbBVFl8h&WY<5%}hn?Wt{dDki40Fk^M)yhYb1vsqYXVJkG49X|0q`_hV&(gjJyE6s}yv=$RRv|dSNI5H;F zyVr`kf!RW#U6{yJhZbAPtuWYHjEZkIsX8bFOoko)}Rj=UTvB{ zdyLR-r&rj5J6<<@@r>i~p`oX7&vX`bX12VYIQ#NopQZ&m`seMct7)bzQGv@#DI|}| zs|(g*NxYUuUaG$?et-c|tj=I*Bv}Anx#)j7hZTF5%+j(%N@GP;5yR#;1cRJ@RG^jl zfrJC>deSS&DJ5Z*i2J3O04aCQK@A*0`m^S!UV$q+s0ym*_=6k@iZ zff>t{Ao2UP-Avkuz?4=8xa;|1fYflz&zwf8+=9R^KNH@R$brxA;UsZ`haaYD9gsf^ zs7c^6r%G@!$MWxuNbITw8ubTmo03PQ++Ri_g9Bc10Y1N-F)qt}vi`cyY0^w`lwHLKLnB$Ln( zI~C{fq$o)$+p+%JfGQ=t4boTu>>YtWp{WJJHhQ#tn1Fix-^|k!YQwjwZf%hJuy5*V!s6L- z8eAVmB|!aYs>&4cE2;EB{xunzCE!^(1G|7ftpTu-&uB&r%Ezuo8y}rV_PD_wLZf4@ z_)ZVxYGk@GIAqhUi*Ng~wq>y{50-_+}=V_b$ z_Q0gYxckW)H>8Z`KY*seYoQ^!Xod}vXWpS}*WmdvC;SC}`t;^=CIotIoL}t-osa5i zz)W}xZl@if1NoYi+uU+VZ0<&74l^XgdngO~(?cQYD2#itIsB>2ulz;W`XE)Nd3o&Q z`aG&Ci4|Hf++1!xi5q`BQpoAI43{V0a{>Ac(+=er>HgyZ`qNh4R>2U+KiX+{{&aZ^ zmPxGOw-S7e{NfQ4l25rLQ>?YgEZclmHl&_`;w6{$r&{Q@T8SbXi+F;7SiGY(EZSYY zzF2E^$f0wP03`FDr%J)N{4JX$C#+&Re-rB0N&xcUUAW;hkHDJD48#*Oiz^eHk&omm z77;sV&zfZi8EJ5*^rv}HrzOUH7eCIlj3X7H6P)FvB`WMV5drQ_DTJh1-y}f#DrTcC z5kD*}-t8tR1n1?pLVI(c=T>K;9&QsqEUbN%1vhzavPeD3)Bga{Ncp3QN2cH@yGa8J z5}!~B%>XxaBk!b=eGWgBATbg=yAV&SRGv)V!})|Xf(diW{=d^S4&ydRNd3VB(R2P3 zrrooX2w!%|{`dzfX#vIF-;PcBVz78!kx1tA~rtwr3wOo zZPBJz`DZmp z%saNO2fC2Q^q^YWZcthjIpnZo{Hf@%GepIi#Dcc+UrCGV`9kKu{(PtvL?GMu7-4|WB=N|-d!b4@)} ziGJI1kh~FHN{HtyxdN4LwFxbL%PsB0z#D&-@|~IbRA9@VCOP%;d8YYnNFHpb>VZWb z&NsMwNpjlY>9>%`KipDHGaHUTkdOBuyAVd9hwv&7 z_OLb{hwpZ0J+#;1xMlX;)JbN$IMSM)inv)^94eq0dPLO+LOe6QD%ICVLDI z{=F!NZ#w|?BQqO^^R6aKgV03K>7ST1{jzI7R72abRmjkQNSEMNHUYfM`LGRmXt zOSNO^`Ym~F9+{FSy_jM+X2+Fa4!E&@3 zR56{#1<$K<{OZq^s4$xs=qVXjyMAp*s~&om`g2ag+>Tp!nEwE-kdH+;sMk(Fa@)`R z43k&Xal(=HAX9?B_lMvO7au7^*;^pb*>|7ziKSb3{{ZKVY4lpULpKDIOd>UG5X^n4 z1%}52L$*GGrP&$}@#3C}NEjOj=6_07Sl91vC;g*=MaGc;mMz!;nEwE>38^yJ&z4kr zw-oO*V`>lw`yAA6Ml#G7-HihR$NS6t>83(Emi82a>fS!Au6_6)tujqk1Rs3>^b{bm zFU-U4GLKSmQ4ty(?aB8!tow~jzI?e9d!A~MmF91r=wr|=T8RytFM1;w+!T9at4PvZ zauJ+=y;YImi<37$?uu->swmneV~@g)Ru}GA`!P;@hR^%dp-_GF1M$!GtmCUE@knw0 z?th&p_RYE;uHMCf{*_#^7rW5aSfiJPLOsB#8%?%E-znRU=ZeIC6nRK|u^eO6b^Oh9 znum+SIR--K*wpHZQB3X(T4DlZ%MU=^YP6cAqz7%iyh;ahGJjh0sk~7vgAwlhs-e~H zWI1NX)~8leibtbMudwLhf8D^W>y3Pz=2Cwe;u`q6^FB|eYOCEg>~7EFS|}5Aj=F7R zl5)rS)j4&R{_(DHZyS!|+NN3KmxGW+XsM9ojW*Tra#_DRi0a65xMs6tK%*-nu=J|o z)SNIQp49!Hn7yJkq`cFd48xb}Pm5Q!w)>XhTd~G#E65BovH(BYrikKSzcLT_5T)$A z!`fe=(%b7zbHGqOwV@xEGG=EVL4jO)PiYS(=||&9{iSCq2$Ra}e}w_7QLzq7q1Zjf zl?M&w5U?0eu=j`ngcMB8~Z#}%TT_ul&uH^xYYZ(6kZ2tgCZ6&!kZ(<{< zX|ep_Hy?bOY4KE0@eR8l_hYR|d^ncrx=X7_y-OO%xobFyMAT-HhZwelkId4%9*B36 z)u3+{&)4kizMFs*1l}yZVe*_0Kudmtq_(iOow8R`ke==o59eAbqsF|grP2QYepLhX z6q=61Qhg3q>s8d}$tiNf)l+EwYP3>KGye3-2ctfJO7s|PLa1M{pkH?+{{SkqwsC>Q zoMYdb6yk+xJ#KJT(8nyBBes%v^xK;1w5=lJXm7EI5Bo)bmTN)mit-jX$9x=9;!vY# z+}!^F3D5MYoZDIz6=$iT8u>ASF-`|#S=Y~JFeokj)97UiD4N0S*P2oqr9Bsmy}b7{-6RSk&v`>sw{tJdj8ARj!A~BkvUh+<`}xZoK@Z zzunLDtLBjwMnpDNA$~(FqyCt}qxHo$TPtFJccg}V@YBdYmTPe#Nsb`XJ6KRMOmYB<&~Hfn6I~MbIi^^Qg_d3)PW- z98>2)`o;o-3k7cSGhK@@nHh}*uyutCYt(e z^R7Jhg+JH;Rd?0o90h!PM#7L?teD)hljs-xO*WV=Q(DVJvV?AuN16Ws>7!@#tBobJ zBWjdE=!scY?j#uUpC9e8{z9xYR^@(R6n1d zkKigMoB&4hez>ETuyYyLnq9Q|Ewd3F!en!hd75%VOw zYd(AU8#s{T)tIQK$#tldzo4lhy0e9YeTfsk>06)3(s3G{j)ATtZW2Y$d~;7yWLyTw{Hmp%uWu>x(83R( z8La@uMb7od`y!yV+(^pA+!Dvo8kcFGs3MrH5}%tO`qDh{V+!P-Owt)4w}}Q%C*0I8 zF4Kfu{uF{>e)Fo2T4UYXq5JI3?My6B9HXfOeJWky`X+w0oOLD6BZhbOSj{+JTrl}y zG5-L+{sM~&V&f!)bQts$!?Ac^GgQmmOZVn0^b~!75xwM*zN}~#h1D?*a0k|vt|A2m7>GE=V?$)ld8io##ZIUp~NQn9mw65-<<@wMs}QmHzsbiC1sSxSV$4sz+o-36+WWIi|?15An#~ObRT*<)4{O>?r!2{{SjRxa<|2 z{{XTohna9&<+W^odDsC{3#EvD`OywKZlE_OD{$u_3~TSjPQG#g0`5NMsl|6~D`ZGm zk3*Wd_Js@boPB9Ak;%Eg)&hNis0&3KXY8Xst%^yZYo^EBU02dIs6VkzbN0)J_F@43 zRP+&XFCREoRUUwY`UToL6t+$Y)_pKrIW16EH+$h{-!1ZB@w++L$%jOLFgeHe@Pg2yg&$A1RdscJY+@azi z-|s#u+eEF)OzwR%fl0Cy=x}{UCXE5MSBhJ?fXq;!`jb-_N#UJ$dm4X~jJ)q}sl_o^ z!@{DErzVd;4kH*LKLJs+maX!%NXP3-B#j}!i2Z00m0Xak^%TH}qz&?|q+}oVYRneV zBL4u#yZ~82roL(Z?kQs2$M35Ov9)o^55Ur4ViV=(0UPicsgfA>=MFxn6w*lhxgSBA zfu_cH?<3q|m4f7xh|`fK59d&pa~47Wd0D!j2H zzIG;mym_e@84n|mY*MOQss8{{3wwfTY(2=87o$pkiA^y>znMvI_RU+wO1UeyKA067 z2(!6n{4tsUX@4z#SVVw#0-^g;xx(5sSnf#XtS;wKkDu$sM&Z#(bwE9aX@$lk2_N^^ z{2OuoD%p(+e(YfTkMXJ@rG6rIUt~-gaJ3(LXOlneK0lQvg5|BZIKr?Wl~!b#TXoFA zm(x3G!sR177m@zS%}lXFbTDnhf4pgh%Bww-pO$#H4^z7zojk|~B4GZ?l`+ySQ10?= z(EA|G2HrFo%P<{>N?JXGUZg4~lolRg_Q(Q>(&A;pAXfFxYCDV3Y`?o)eQQ1~R_fy8 zXjTe)gg@a;LGC9a&-OwlCA+>OpSl450PEI`tQNNMZiuP>0BOJa^$JHW0THtk>w#7h z-yj(kiSz_gcLib@bv1~QxS#h|HBF_S@Q{q^r`2mhRb}B&0qg}tBXv1I?0pI1uFAq~ z^f0eA?M9!Xy_%mg?s?CqK&^>mzh=M)gVU`^ZwthJbz;Z9IHu*LiLsnC z^KmE!3%JmK8Mpu+%CESN820V~y;$V_b#&o+D(BqPPW=Ax#s{+IpwOg%xyhLd`T@;8 zSsoA>W8Qll)0<;wYVrN!z@`Z{rcn{bc`7qP5hPlL%VYf&hp8l0gEE;GNBLW!7|#`x zHn8y)^6e#&y%lJ-n^V0dg_A%%CSb$z2Q;LusY#l*5n0Tw`?b$p^akwSAFlv~Ne z00w@$YQC*)t1JYpkTM=H8J8bS156$e8jX}U3$NTqH{LNQ0H@K2%?izG#k1Q}TEyH) z>nSRM82qJ13f22-glQy#L(VsD3P9(zbJ9m;VF%eB`r%JeCz+NkJ?os*t^P*3e-r8g zG)4p_+^{{f-l4_xEj85WH7!zP@f=nWFpqpiUx_5Qony4S#oT8T zTe$gHb~~|I7uu+n(Jf-}07Qsj3xxsS-yo4$@r^3zJKbulb0ZuDC|!xoZ3U_sS1J6 zDvXo;DU7AbTbVr8m+tQ)AAl7#rIW;tUReCs95#D?X00-QVi>L)3T=^(Uz#pIF^WY- zm*f&E1^G{q=NbA{ma$n7d6EYQx0qCZhLLS;AO%uVi@Q5Qm%;Bv*u609%dsH`f zP}-qI)-4`DKe@POk82j}fl-~cMQySiK@RRD^;)~0(s3SeE%#1R<_+9((GBPN>AwoGpfWOX7^arst~ z-0D`g#yGDU7|-`YDIY_O)ej)~jVWU^jmtcZkbjj*zQ&Ddd)$UhD*?i{VH)xJ)L~i;h`vPwPpwZ9yU!mr%WBvxe^J)Gyu2frIV`6*a}4r=~?3&2WmO z?OpR^{{Sj|+({j(Pc7xNfruI0H!gj4o|RTRKi`H_MkH{|$Mnu?IPdP7$=W{frS4ea zog3+s{DlHF>m$ae-!*4#;$#;aPfk>iJE`bBgf}J zk|S+kk_N&0)QZJ%i-?uG7TQfUA-9e(`-oLZ{tig`=M^R`LE9XW$LsU|0QHRtyJ`;C z(1?d}Q0gS(u#f>vHumZBJ=|=4+@Ix|oF$_Y3AU7u#!P;=sWWu8{#~%Mk0&`umCxsz zO|&$V-Pno~r$z?-SIaD<;wYPgo&ypyP?kw#2@Uj;jAwHr1^oD{328d9c^xDz$r}Lu z1v#M(Kw=a9lE4q=N-9^oW0a$7DE!L8FPm?f0RyimGoHFq*sD1DU};ctQVT#xBT*kC8;^IPakewCyn+yM)58v537OLc#-cOc!i$2QZ`Op@2`BuxXtMY2$ zTWHP$5D!v0{*=Y2RmVgi0~}=|II713BaY0SfHo;DZ6<->oV%hyGYG9==Y0Ox5KqnG2GC>6pp< z_@?iWpSdv4b{YOuklQH6Ik@SON@1RESy8>v z5Nd^TxV4@}{{UGi`hY*JKl?IrGO$lC`&;lS%cjgO10*sh9d|K5fg=?i#8N^QIcL29 zzDkB;k8lt4sFQZksKqy^!)&TZF}Kj3pN&Lq8QUovW7(@-2)r+88p-y%iKNEX^EU?k zyMTD8Z#7*x&dUpHu-M^>WHJ1ER&lJJhK_`my$S;lfMQClE`7e+=?ri@6 zDrb@y<*rzJZlG5vt-RaL<%F-IsR#c6uS(Xj%9|S_AHqok`PPOmA(M0~6w8hg_Zi1l_ z^54j{A~r0eCulzYwMfT$uFrLK2+un*7avTTBPl)Aij-otQX^e2?&bG?x`2N{QZ1t> z;1l@c`c%mr+kQ!-Aogc0KT3a*bXRxoO8V_T<62zC#X(`2cPKm0_m1IGHH)A1rEohh zKcJ!5i3Dun9RC1uNd0N+0+Y`TAnAfiCZ=3cTii^-J868r%<6x}uiDyL zPcN4x%vSjCK0t4!N>nJ~M*~0Iq>PE&#+&6Qjl7@fR_|@h^lt4EOJ}1)H1SD)5iww^ zs|~I_xvEzm*)d1H8|bCU`>LdxwEARmW#q97>ckv>Dn*JEIZJQ|=vOtIRQ~{&(r%j3 zM5!EJat4nZTh%asT60*UuttZwYl^<}pOvMk!) zM*ErImgX`(P_7gDQ>MRNghisNw`2C7=Tzf^5*2m;da1=q(7*@=P2XSswI>_B!A{Fz zhFjJ-7Vs!Ny!JSvr`ZzxyWfvPMOVz7e2<`aBJqLw)NdjljHC6aOl;iX{w9!xjC|aE zPCuP_x*mxC0NpooPq3#55xOZKUMj0z+Cn~Sm{j|=f0b01#9C}8A8Xuw5opwWz0E^w z80X8~_|$C0I9SwvKjB%?Yth`F+{Em^-XPVVwKrB%^u+?PI?Dc-`Y-s?*K-`PW9?be z>MW-SwMK15>9c~sdsT9_9M(7FmN?fg>Z9D%88y2&4t(vze{@$naXrK5a+CF{4GqMk zm5hJiIjt2QO`l;BY;-s&Z?0lO9O?Nvlhx!yl`0+M<>?;^A%F`|YC8zJqd+S+6e~XLde< zqBl1g<=C3kw-9xa0QO;1Nq=E(xZet&!j5FR4$`rL*Ee8ikUi=nb$)*9AN%Ch`;B_h zGlt*nqcxu|h@Mfl7|Q#Vs=9p%)pa4B_VE|znYi>7k8!PBqY#ZNANNgMChG2SnOE>! zR-6`OC+{WzW7JZ#M!AkrwT(%S+`% zZ*+vyv`OZX%eBSyzcVwD?s%x4=0U?B(zd{R)xh;Gs6Se%Cx@nyu$pA?f3-c> z6*h@n@@6QRTklE-=T5|IIJ;1N$*stIB7uWXcN9bWsk%d2QFwmRHO08RnY%JtsQpbV z#I9)^yf#fC1YGVOiV3T4rH?P>X^Hk)*^@=pKjn&EDDApMU(gCI{2Q!ZF!SyujZgQ9 zYJAZ|Jx*b)UMI@DpNPd>gKZ=Jr@;P+dVUH_OjnDf*nEqI)6T)}5fEcHUud69v>0Y}hhpZwQR|$Lk+vWH|bd5k>}ApC&-wbXr{2!;|KeU_dh zn2(Yp>;RC z@66g2So$t1UfK>XU0G^h58KYUl`?&C{xt~j?V2B!-I)9ItyI0a5%RGw`^+k|_ZJc$ zyuX1sr5Q+WG2d{u)8dB%W(TG~s}hTNQ|5Tk`{k=Y{Uy#5X+Q5%4VA$;aR~Q0CY`m_ zhca6tE1Qk)wkMIsdaCUd#CbySgrD|~YRB0B0Kd7uhXR9d8vg*x7C%<1x+7QQR(pwa zhY>H-ieuiDTq{MN_r*-mrhZ#QC*0C`GXe5N1b+KBQE@c;nXe@G56WbHPAX(88e!S< z=ng8$jmP@?qx2Y2`U;*AZX4u$pHRQl)3}l;tZe+MI3B#3nkXYi%%?ubpVG8;DO~Ot z_ac#FjzfjP`Wlr}s;%Er)zJbcsS{z?s35nf0#e4P57$N7r5bmc_C7aq~BKU`B6Mh_ro zh@W{bKcF?DaYk)P`3Y@fa2tDGq-W7`e@fYgb8etEp(~$2gZwHOEes$iiXfxWiK|QH z54?b%_DyFiHe6kiDAY@gB1<9qRZGd4lQO^*f3)JP=XVIZN4{x~0=W6){D2fCXQ1UP zn8GW2hQg!@bJX)!W3gCNF_IuXqZE?b+JbXF-=#JyOLix0?%(e=N99!ciAp5zJaMit z@*hPYR($ujIm8-%MtZhS@)e*hk};Drk?D{HOtKtpjldqc$)dwBnq5F)jgo#$Dzj=D z;Bv5OM<3sL0xfh+1dq$eKUzhyfsf9_pQToWS@bzei~W8zC8f5A{{VJ0G9S#4D;@Pu z6*R;p;|3oSqRE!RH0(ax7aa><-*W=8{MtdkS`Om>gV)Subc zGX3j&SjX3MewErSrLyu7GxVzw!53|#=qWmb_Gb?kiiG4tB#r2?@mJ%~t_BL)NrBTO zEp@UuiFiM)ReQtVarOe2JeYeW)WsS+$B!mFcikm8>C;387TkZjxj(IF{iarIA}af_ z6t6i&%gxWCnpb6Vq*0F2>p2od82>U&vNbh!OJ{KLd)1qKtpb9p6)$j#BPy`LK?% zZuSjLY0&Vfqu(`-)0oH+*|Eo=sS@^LaM7e=(7sRQPQlD-BO;x}Gx*eu;z5rw6nX{C zW_kOEEQ9>})6!M%DE!413znTqa<1+4AXFk3k@C{W2cT+^W{3Eg6G6R+ILeAZIH62! z&(yYRK*Zo<9{9~hFC%W4KY^(hWIYHJ3~)~5?jD5Vk)%BpV_$R4BLyQR^Z3+lGDp~- zprL>pc^i`be*sWROk5}q-%(BzEUn5jdlG8Gx~Sq1C_rbHno)@hqWUStNd>ei(5OAh z1&{Kpz`GtI+wM&%+N0-geF33{y-JcKTX~X9dvGeLiZb6UtiOg$UX#j`qW*cPfk=-L z4EobTW1wK87?JVaumws{ACo5EYNaHho95(tXZ$KikD4~5Y=7W5{Y@@W+_VHJK4(+) zrW?C-2j$)LT5{Sd{LZQmxfLiW82Mw4kQG|$2?iG2k8>VrV`ICJ!_Wn(1m)iu0DrQy z=Tp!bTj`Na2yp41e-VmYmN+r79>$pzFP!i_+nTV<$h^4x1tNwHMgkB$>F5i6wfgf{l#B)GywS|jPMsfjYZ}K=^tN8nOHdDLVc7|9yRKG!%RZ1*B>(CkFdun z$zlh%%`kcR!YZozv8Sh*{AB+Cz(Jq~i&czaWN*U)lq)Yq&(zaM`MzDp>rH+5#{7E< z21sNoFit*(o3u&KU#QJj^4J{jAI_I_>~^l>^cbWfOe&w7pJ9PY41e>)4|B~|dsl89 zTOIkuPZ}gZ0Hg6ap@5PJTZq|L{sUFby}>y%XY{K^McwkSC(sHCuikWQ`tGKftX2Cw zi_YPnS2-0TL&juvC;gF%YqR;85e=ihb5kVyhCXk?jYcP!iNY%r?MMQa%d=LS9R ziE+QDb~O{+Ob#RR+wrL)gtz+4F#iB+ikV^veo&+7OVoaaMUrF5j|bMCvM>jG6YK>} zROjYDl?D_%k%}}HrB#VuQ6j|Ky;xP2kVXdZYDHMlcu-HNG{WEmxJUU^+@hVt)`fr^ z9P_&?`qL!)Do43WNJnDXrJiWpDj9cn;8q3H*O16SvR6HrGS$U5q7<51v!aGrM0u7d zl~1KqmJ4$s5kzv`41X!CyBRL8E;dZQS`N5if2DO+i)(8b^7ayXp7k36miq| z8tYQM`$5(YdR@U3H zOS3T_g;F|q!~1)t`$V2@FhZG5-1q6**qX__@TKM?8&Y`?{Mng_HJtiZ&)r@!_TmG+U%D$4rA=`U`48EQ!8vg(cjS8U{ z^r#_<&7}|pjGT4kGW~JITM~%v3Ru`dBd&jZU4R_+&ozF|%FW!*+F#*T^BXq5;ypm0 zwOk}osTg&L0uQmxOC+;hDmBIR<=ddbnHaV{hLvpYB#cQsv5nX}P;JNOoYmWCK%F3l z2;_Ih1bGa9OjA*eb+Hbl<)!Xak91NMK`XWn8c3oA-EA6{`)HCTVM;@qavH&6&^CnqPq00Y{WwvFtDsZI2;oh%V630~D&U*Tmv zKRQ-Ys!PRkByJbW#Dso0;-j~{jFaTuqsltr{z2NUd4g!kd0CM43O-Z%R+qV((b$a< z@Kxq=JuVkN%9O&vH)1`c@CeBLDG7oi#bDS7<+hbSoiNL3w8^!}$1+F`0Q%5_akIwk zhBP+HScA+eTkzdglGZ3>63clIWbPy6{{TT!yBWt#(F4bqE^}4lmTxd;%0&c#uA8&Z zsimhvn|m_&kzD6=l4qkf(4+AKH5M&Ps!1$s&n+aUAC7pY8-UI-=(%hz1_d|C5geAv z`my8i&oxf=C1}d&sM<&~1WFqveq?6GJx@-1RhV6tCTBPYwlFF<(%nxWWb>1Q{J5sv z2=^60azEPO{#20uRvfIpQx$f|9Bmybu^%!`nF9op&5}(-9E^gj4@TR~JUsBYYiZ?# zk^Bq*{AhO_`V5k2+E14uPqFJwB_EL($nF=f*EGo1cFC6NW&ygpWd0mb>#|Qh=PICJ zasnU2){jxW$Hy+?gxni19kIsTnzbICe<0@GP^Z6?%lgwG)$i?~G1yH6d2gE#fd2rA9M!-0N<2W3I<%I} zk@FK7B>hHxd90;5r=(3|PAzL9z14-yrqgi@sO|SiA9|sgE>+BPA_oH{FiG~#Mk=k= zrK;Vl!uPR6t<-r~1M{V~uv9B6>NCwa{w>6T>;N@!b9ptg4purWKuyh_02Fc|(+ z=~nTupE{BL(82uba{}O)=ao)!z=APPTYbkOKQIogYV4zyO zykQX$`CK<5Md1Ee;;hfB-A`}i+G&7j(WK1`ij=ev5oHTSX+r$0xaN9_y&KS4(Ay=WB&kvs9X5vA zKT}hYGn|-T{p6OaB)3lPe20N?@|Kr#(~MKL0rMP>So8YUsTsGf_bn@=LmpMr5!FkA zKUxrO1>OLoKYm@L56Y^Y$%7_%?5p!o1-z$oM92N71fR%IBGgQ(=XBQO_5pTd@y|5> z0JPg8uz8yUld(UMs)cR3e97*}arkG7dd93ahjKl4g&)d}gEVBDLyi<>)R%DtoW@BX zO#4=C-Q93N7{$pmLNS4k)P6`MoIielAJkg zqD~w#k1YTmpbY*-qAJmy>J@!XKtHLbw&J;UNB;n5I0l@7M@R?fxYe0lQ1>iPmu(9X z_fMF)Kdnt`XL)%u0IW9jPOIzDV^}6q0Sn1lL?kbRk=K3Gh5F%F-J6qhpG zsdBxEyrP3FA__m*Khmv2bV#BT-)b?h7!ZUdf%nMGR6wRMftdXO&*MSbNmdDu?v)_^ zxfD%Ftr$VYJIIzDQ$&qiwD=7sBV=$$UsAa5>Dr0)>w7Qw_0?qox^1^oN7st2(Lp|uFX3d9%Bc)EVIq+p}6Go4AxsOap`QBcYQ4gqD| zns9|x_LkS!>zZgZWZN)$p#K1LXFsJN^8vt&XRs(a zs^o=S#@6bmjDk@9eAA1{vEW=TK83$pL$LdK?kuE`wVkY^s)l@H@~W*qvpWTnD58uE zXUg2))0!g@E=x}iHeE?cQs=kJL2)U6$% zU}Vgh>RmJZ#a4BYIA-B{l#r24Tfi8Mxl_|QKgz8WV){)4NL5z|I$&e^Qw7{kaEHBF;7Wke4WU7>e1Cj zmgt~Pa=q95O*KQ7b!K!q$sml<3tY6*Fl>J31O1FsOsYX8Kt7>HD!QtY$L(lA{udih z=Tq*IK7K|X&VQ(;n3}N>xQ%e0c~&QkIsR0@VO`E|=SMx#Qoq)nJ=$asF9*Ib)f-I{ zqOvi_Jb<|Vl`hXlQo7XTah5!WXbS)_&*$(5OZyvIMLDnf1Fq=B*hWQ>0G|BSb1>hzK|p(z$f)F) znDQa{d*pLNt2!$Hh@Ha+xmulxC-+1@Qt5E3> zC(ZVF)940jBjO7c8)IPK?u^uJt;;tMT2CSM#}xhG(9`muh-Y{h9R*UjxrQ))Vfoc2 zi&wimKq6E9<5gS3`h?B%+~e3{oKosblP7Da-Hr*T#SxXVHy>as<&TGq7&1Qd?^_EG zkf0(A{#4wTL8N8JU^e8hIPNJL$$|M>w?SG@4aA>$)mPXPQT&$w0LHt$aY-<-pY|DB z)cp-ZC4(W!joavJMfDhy@5rB(SCdzSt`bK47K^cHQ&Q;I`z*hJ&038bQO&y|j*$C%ZR zqUDWFH2bTi#8Ipu_t-N{U3DefvCd23O&UD;clTuf0J;zKq=&;+8^7cF=#M}#{VS(~ z!smATi@8|;0C#=|=~~fgR`H-v%s*e}RnAI_ISBkcqQt?ZS>-?NNPneiTiNLmoJU~4 zR!si@I@^;>w;fr(FeJ)Yu#X@6(F_x6Kwm_k7E!XY@C*PWa$yks+QIcu~NtpKo z{py%aB!h-U{3%~ZytNa2u$I~=8+-i0)b)|E{VSwWpTQUC1uHDL`9TzOL{^cV1bTxg zGnkXva%#@Pqvw!*RFhfVPCwT=k3exyN29cDy`&6(+j8HfCdN!-jgKBg)y zfTxKphe8kG{{Ysi(3`T)_Ov+r{{W6E2`=E2WZVT__^4!iHW=HV!m7QUw7mIe@+O){ z9EqM?IzO4D-f6$v8gyI_EPuQ}s*j`FN%xXd_~N4)1*dC9KZ&BqT>eFgd`E1pwjZ;T z^(XlXo*h==QMrgj?S#kksv|^&56P#n_cCMp(B2Vx!+>++OsO-&361$ zR(!gQPT#$eNBy?v^rRjYh@Y6CAMX=Z?fg4s5y^>P>qokW%_dZi;?hR_++Jb!$*l-v z>4>C7`~_c%3wRd`97sBGifoB$$L9)urk64wD@9kjwomnqD<4eMKifzS$Ss@d+*Okn zmO7+a`p~Or5ALHu?iP&%+`0^oYrBqis)O~ZNR;K_r7NCy6ZHTO)|JLG zIX-S=4eiOPi?{Ho{BSB3cR}*JL-MM#TwM``22b}uqQK-^d#@<`%@983nlAz8&X4{8 zq?)H4wZw;WTHyMYAK_M()xi0c6nmfWp`;|(^LoRfcKVPiDK$Hl2Wm)v+)`=m_O`<^ z$_70+s^?a-knD+k!=CvxSf<%swF}8u?paiNoC*|AH%VtF(|7*>TCI)FK2=D`{{W_W zrnS88G4_j|f4PJo%Btkq_bVG4)5_ovR{sF?(j6vO{{UL;pZe%P{VHQ?GK>aqtg@E= zRGv#O-@8%lqx?-56_E$_F&!lYdJ~`LRw2@3L6(dkp#p;xt}z|kei$FpoAw)%%U((N z^F`lr@}lZOv3=D=9^{&NcE%P#pJ7?AV=g$nZ2thb4MO^R;FiCW=oBq0F5@oEYjH0k zX$R2MBF=R`H1dDnRbD+MnB^{kL6LrbE?X7aSRqo>SYAduxM{>Uf3UnLj0Tn=jbaf?m~<$^1FYsr2haa zTX`AV4Ra%V2)6#Fiw@|~SWi+&AMF~ILJkWP^c5UwxC}0R*yr@CDypNeK8m#?LzI7X zAI5_lXE+DzOl9A|$oiUkK=|P2=}4hKU_R&MaZgzSoMWH&vr#*k!OIW$6+n`cCo&KC z4Hp9Od!OM%1Jt!f!s0=V@u~c1GcZglGkfj=sZS*7$xq1S(nBX`-X3O|Pta9n)GeLN zV93lp1yx~rIpJML`$Pd&V;)EbPcfU(Ii@1aa4gHYNl&RYZ7s@|QjTYlu^p_6 zKfNOWKb>1fWgk07^bMLD3lb&+azXl>)WpI@7=!dRMti7Het7`;b5tKww-RlQn4k7m zm75;MyqO>Bh*9;b?<+6*#$)sq6fComiAX22Hh)1^R>~z^&g+j{QFIm~jrV=tKBlJm zan8f|=Bdcg7TU6B9AHzG%xn9&N%wPsKn&{uK5j;xUSLQ$LjMbEuLb>vskD#Hk4Escw$CNYm%|j&aP6!}+1Nalq|FB|<^rCE;l3}P_s-iox3YzV;%&tc6G!6G-vhaXTWv?T1rl37^s zZus}6ylm0r&E@-GRMNbWkDr72)H{6Mak&2gyFx(OMoCiK<*pgepfx?U>rKJ5a=xus z;X*iN0DT1{+cAJo;Ay>d61ah3afXeTK7xY;Q616beMzX}DyJcv^{KwlF@S$MREJX2{3oQ>}={-yr)@bbzC57T(giFa7qT}TlR$kkMF_Ll9 zC+bBo>Mr^6;?KWInJ1D|{o@7p+6U{3FJY9Vg5bX5ryuWeQ>hT34X_`sYGHF2LK|ae zjF$fZ3eMD?&MS73NtCzu5c5q*KOp2~i7z9MoUi9mrlT9mBQvNcpjFANn@c-gNef2P zCQm_HHo9$%l%KqwV&0rk+!EZC&fTnP2#7*=vwL)=I=(O@$Fb&+By%wG%&NYuYKE(C zcW(!qZ*-~ewOQs;mE#2Mv(^nDgWx!}m%pw#xj(pxeO7tyn7*yL~_$ppP@Lav7~oue5pvm;3z*YM`2{Rfx1dvgN>E;JNl{anFYrf;kQaIg^N&F8L32SMad7j9g>~aYFxT_&-PGb(&$hZnU@NrT{bp*H&K_hd3%yK^pVioPCod}*; zw)aj!{V0MN31n1}8F~XJ-X7%Bw#tB#j0x$zvIpQPra2#moo~uar2S5&{8Lk{ySMV%cVhd z=Y`%`Nc;fBL4B+>z#=^&-q@4(gE02=s7#j$bQPtJRzJiXWP5UH(y2_jjZrhe-^o9P zXUVP26!fw~qBCsFp%jJ74tmlwuPG)-)GGYNswn50KorIaQ^xne{Y^QUlpVw|2cnQ_ z>`luJNJA=As z05KooOfpVn!HrCR+*6)@wG&*1ae%-MhaiJaNsm7$3Qtl%p&+?vxMj}Jqzo_wlTKGf z!^B%Bf(j4knua^MS1yE*cx-}ytpGI7xp%1V@{G_e3o@H!0JtZOq~kdJsoSCo07fc* zi9zY~r~`RpZeUVQ2|ORfQTB*bkj)$M!9ou|N@R``M$(ZLz(&prg=XFO9x6#~=8<=! zLIVy%MpcjGYQNiJB*O9BC^kO-nWS(Wy<)e-w2XOxYH|s)4 zsKoEK>T6bTG7xaz{d#Cibz;#YtM({!`cgd`q0(%5}QutS*==j&Bf*1 zuhS19{)UzvHU;K8IF$EuF*y3w9hEfWd)wVfRZF`G1v4>dV8+A_3iF1b;3ljtO>$hU+Wek^GGWq5&kb z@C-6PWpaO*H1IB?Og!cG`cRN@$uDZT?hSzrO0<T2m#BzIh^godh)?P&6FsblEfODgUBA@-04f$+P)C;rap@=@ z%up~*MP$w)bNX3U2q5<9$NR#VN}TU454V^I{&ZML zh-9}$&+p;w##EonH6ZfH2kk1O{+kj%P%}*PRxEj!dzTz+jj{NX#W38!tGF|Ld2i5) z1AWU|qm6Rc*33WJ!w=<}gxp6l1AH?9&>~~`)Df&Q;zF`ZpYI*N)|^eN9wCui``H=& z1q0KeV9rn|Se)nmyaD>te$}@i+l}7upX75;G?4?3w4*8Oyc~)nYhFxDOLt_q&-I{0 z!sXS8^5Vh%93PkSpu0#$Bk%Q#KtNqg4ewd)ec8iq|g*~_Cs|%)5 zG87TiD{d6yb8ZMN9B1hvQ$T7x4eU+K2vGk35L5i>$W|-&Nj~k2{$i;K-x*b#9lv-8 z6xNY`>72HIgy7JS6R^4)fB+Bnx&Bnvj_qAon9sJtNAsqx?lB-E>O~=98$1K*9A=9J z$#FBL(D97*01)4;H3aOTe$sh3+G9V?qcfoxGYt0QC-gMsmLM`DE$Rp$QDD0ySDrNh z;NbNmKdx#gp6RgLQzN?cr^j*?zG8uUWFP5HdD20W>BpmE`OwfN(8Bj_q5o~*kwf2mRDJ|RY3TsJ#+`m1xf+(;#H@MXj$OgzbAK$?gG*>62DnH$S=|ahY z)=~7w@}&>BawH?&f5M%G%rzu%+aIa-UOzfn7DNP+(yzQ=!IzecXBSasA+EuwSU= zl(0Tx56={!Wq)@d{K2Tot7U|U=l!9YjiWu7XVF(br5vHmO31|GKz_M2)&%4!f2Pnu z{JEr6U^xW(;9`b;PTk`@%2CQ3!$c@XObegHQBMcfNB;o6Px#SCD7l&K>!ZBKGtVP? zGjUcU(X3)QF-A|R0M&yx9B@7ACY&BT!JnpU%+&PsBnfE`2^tTgQzX1d`YYqvaZs{P zG2&T3{{UriPmaO5yrlhkG+aFg$2v%GbNJQx)k5d`R8hbq9Ch`@54iPw3R{$z_mnRR z)dZJJgMh^Q3XQg|OH9Cjvw>CK;>la)4=ML!LJ=I-*A8}~kHpd?wcNv%Jo|A{jZQen z?ybTPTndHtrY8v*C+k(OLVZgy=>VL|BCoRYYGY?-2;?)LRy46$tJ3s zYulsy+qpKM?x0cm(Pz{z9)_*W&Yf?cG2&0Ds_|ZF_YH`Mll|4rSBpclRt+kd8`Zh3 zNMVj4wkXsOrYeL@2|zwr`1io6T3ilxsP{DkhsQF79>S>HTHDNVB-1v3y4a;_j`l3v z>lQYHGhM=;jb=ZKwD)b6^BEhaQZj0@cyC8}e90~4k-c74tqWZ%Pl%5$-4uOEs;4E>9S)K$R&W!m~xiP>$-pQ!+D(r*%PStpG5pPmy>#AFk`?8<#6hGll^P-4w@GtwZigMmbA;fZ% z{_0cIE`;->lw?IK1MEPg@?F0AGLL5Fo?*V9qsx!VpB3&Jqk&^jBl}CPIDtjd*N`Rq#8`*kKIM}cgd|szJ+*>31mL3a4NIi z%z8wqKNC$!iNSRuT`FJsX)J5(1r210{{SsiA5bbeZ-Nh*kT<)JZ8A{{UnY zRFft0qW9VDiNu!$y|)2R5m>}B6;HW2towCc{6ODbqO{{h+A@HBaZ*yTSnF0X$Rbaf z&+wvVuiL!WDhzQHQ{9C$G%=j!CHiAD@qY@88Bl+~g#}odtwM~;Kj023gz{^fxh6S*bJ=iBNG-K{oO$<7qy3pN{Dog@t7~zPk)ict zNH3N{pE@8txvIT_((XkDldy(siR5no0K6uxt)O4|WuJFyiM2~u7w4aG^if&&J}|Vm zI9pqZ7=N*}ezdiF0!ynk#kNis7|+mVttE=%G6Wvju0{N3bt%QZo>$%T{VGVWwM*`0 zlt!ocRS)>n<$i*^PMT?Ktwvf%*XjjP{@l~|6p74@{>l^lg;j@1f=o2qO?P1WHe_6wkF@w?0yV z?%&}~%nPd%D9iKpG>;-6$I3q()_jv(bz-ad)JrQfU`(ui04MoTVO0E$@tUE@CL zqNG81KJUzZ`hJxRmP;A^+&42HPcv`pQbd}PfFjdlLH+4&3I0NcG>OXU?6_Br2m6`+ zW}*8AsVV*C(LYQIw2|ru{dD&65AMdKpUj${=SBHL$lk1QDtW9`yvot(Qm7l}QR;D% zQV1c0Wu|66!mHe_rywFfDnE0RSo7%GrNJUiPXPY_3UO4G_a)gH)9TRMoDVeP=qil9 zC5Aps_VND!zDYl&P#zDvkm0Wz9_6asJ`Pw2c9=7Il+W|3v=KL(*jTms?@rrTE!{**qmYi>w?H2aVMsc*6_-Nrc76^KZq`jd(c z#K@n#5$>p)#I_`mvD``i>JwEZ*I|6@v$*_HlDp^+PNl`v2TflJ8QGWl#n6A20Q)P`MobXGYqTgNUI20KV9P`U;8l8OZ(X zLd8#?U`91Q+rh+TS0Lyi7N$jkt{c}~xU~@VIpUgkIhjH}@kJhY4_d}^r zJpdW3h>}=FcDuLzHM#!)>(-T<>JmRNL=)G3Y956}5#3zse|8IRF#5ct57MeEI?EmA z)*a`zK>ba1P{$|$vh(bwrwFUehaXW>&blJG8|rhG@M@)h(i#ZY`>OdrpsgsC;s7ja z?dsh>TGC?{I4ku$R0i%8I9by^i-SqKjYh)+mz-rH1L|sUB_sEeD9>Oj6FWH0@&~Re zFF%|tjOW;aQc-zKOVDKc;F_?v3AY=+JPOT7%)je8AMZE#RmiNNQIu8Bu0O_;8YS8! z1npB*oVuXv##+Om_D_mNY`G(y4E^0Vd;hSjTet>^kwevBUm0PkQ5xMc_KC}Y^;ieWQ19&k@&T$)IBXn&Adc!nsw$jwrk z#oe%P91rfspjEYkPNm(y>!ScaDt7Z2!I5+5IiWU$(q$O5=;LgeCeQP$CekSeHptw6 z6JS*1%p)d2kEKS_M48?*_|bc@rDR3+86g>r!x;A@(*$9g5-@ZA?;ov7ZUc|q1NaJW zlJaoUZO^_bh_?}rCCS*n$~mZ~z2gz0lj_E+wUiP3(l?p)kkUr+X9$1+=)cs|k76<| zUGiNOPdo~_6jC?=%45`uZ<5ZymnuG>RL0pt51qcfY1~5WNdDoBA6_W}%FUk?orPZ$ zj~~WKQ9`=A8)-peba$5ujP8&UVRUyhQo6fAKmi=;@2UPsA*`$%4;@YO>|@VRn4>Y4nSHLW}`mHc`%hdThXIH0l=n0{g?dJ-($ zEpr25I+}BFawO)U9;&B!Q_1n2$4FVoubsJxH4Z7bGJxBd*8(E!v3&!thyY|r)p|c| zP*p9}wFiIxlY>#MiYHU)kII(ljzo{|ZH+&x@#&L23SX3lM8QZ_#Hfe5=;@JTdbqmE zO*Km)GYynkI#_HoJ01>R@oC?7XbXoJ$6`9jUX^X{%7gd558A^zOSRqBI*|hG<16Tx z{U7%V-v@an|A!Qyi(gbKwX=UT=gK3g&-67II$^&^84J{iC=POYQRpt8UDe*Hq(rl% z8;_Ch+|kYZIsG*Mwh8H0hi-WkQcrxv>4BE}I0i7-@Q+|rTh_g2URlYc+2Wpc&1%`? z9iKWuU89&-uXs?juHgrV_#{qMm9H{cT73y8J*VzZJl0s8Q?3iYOTfq+f8PmyW)#h0;ePcj%uSIS=7*H!nArEvue4sE|r#jk*c#|I&Q;_)3KF<%aM2cbj=F!TMpJekM8bQ;8N z5I%#O7g>dta2<6L<2|ON#rMdHf6yJIYkW<9nh03?fBTc%nZ&StASyWhPj%ionfsfN zHEA_3?fb)mh1TAT7(V-UzgCE8TIu4a-4N;0!-k}#ZC22hGS^B2J<}DTk9@?s5Ay9# z!Nj^Frf#f{lAF1SN#BawKebZnZhmaYF#S_K^B|@bD6U_+BO7QraDRa~6vC<)XuGO( zE5<074*jRom%~DQ&XOt~_-Jr?Q%TQ9-qUebOh=$FwSNVq2ABx9QG}gr=;v*ac}E)+ z49@Px_>i__Eb?tPwAn{7piGXe#HapiH)&`T)b5sv?iRsZ5}|o^e^{t#qbRL$-Yc%t z6iZ^K)!|4*c}3l@{DWW(3iIMR?HZm18aak?wsHXLVv?&(fnzUq(5 z+H&XK^MAJADxPA%z=rjG;*^=zx@ZxucjrnH{EZ8JgXEOIs&ZRX`U(G5Fdj4I@OzvK zpthDy{EL$h!e6{D$58|m$DrM>6a(1n^Ka^Y6|_9l8g1>N>9|Z_ru}o&xOwXcTQkuR z9Z}~X6bQbF{55B*B?mM0JCE&;lT2CiUZPWSLA2u-F-fXotmiyg<=#nu0?qWTLX7^7 zDRQ7Lf0ROe`R5$A__}!igksL>Ci zd{sP1^Y~l$>0Mo+zoWlOY+uqDi8YX!&KMHNV%y~`;uhxH!|Kbk#LrzFPI$38ED}pa zl@&cKbK49B5X%HXn_v%R*x3@qB-_e|c|lYPao7(kS8-ZcDCkWViZ#~Z{)PEhJpBks z0)lMZXBVQ@s4TpXe$QVpHgc8puL_Sg`i0-}VS z@mdHYYvk={ocZ0C>Fd4ke*O}_8t&q7cRbZhNCthlYBnq@UH^(nf%aFvla!#GfSDk8 zuUZ8wpZRuQ^+FGn3ciZAa}>C@gQoKQ|7v0nV7nh89N5S>DXkL&e_$LNb&cH#$G;Ks zLhFYB%Toxyc4W@vX#%^6d%witHxvf+*(2 zidj;;@%*yPf8*z${Od3IIN|LF@I|^4f%FDBS4Mj^Kg*X}=t1vim8H2B^}&IPt1nzk37l zCuwBquI}=4u6#um@Q-MJBQ3Xgq_F5VwPCR(Mr5o<*_G9UIPzM2)Aj#_TJ&uo6ww4g0WtmJ(t%&$+6@?0E3;=|lKvWPS%BAcw72vm-Up68!FA#kj6{JEY~LX1O90Enf7i7+Y%wHCQl3L(Ec2>Y1a% zKlcpO^BElaFpGW9+>dJc7PE3ZFbzv1ywRmJb6f|bS~+olNR7^SiqsCglmtnx8O7>= zZa-q@=pfQ)Pu$U;!1J@UjHrAY3kHU6lkJgwwuFAQSeXczq~0W4L7{!U&A%fUjwC^ zLMTRl|A$m;pH$N=9CwO(0b^*kUJ$}+H@xEhPCMw6ulR?9QAcMstrM>G8SnbrduKOW z*3>;%T%cY+qgE(3J+22;BBE8{U8!s;a~q_mI~NXdREw*n9wR4hXa|BYi@W$uG7Qf4 zBc8ZC*bj_SAbheLR)JRxwa1jUw82&IV~W9X7FQCqG~7<47vw*B?#&*9ipUIr zfyhoo{2=WxYN>QyQAWO_(o(@#F|qwLhy;<=zgK2B^}k#BP>rYVaF|cgJtt+iAI*S4 z1IEUMr=}#-5V*?4JxETidJ>OF)>6~q1XWxip9L98vxe=pX`NQ5>ysyyfEf>Pz-EUL z%fGGXYJc(d8`4odTKQD!7d}G$F*Ewu-^c_BUeVu>qaln+m$9(Vt!PTnF|I0z!AO3t zZ#6GzA}=R7<&y_95dR^u(N+rCjii2SELc~2g7g4uk6}MR(BO7lg>lv7#eUmJ{durw^r^ z7|CH=7wrvkS`m!*bD_snwvW0Q2qoUrBEQ(~I)pF+wbm3uuJqvG6 zSN$Rw&dO6NB!Q+$M|-;eIQ!gbbjeVlrAJZkS+TzP`tHz?qLMN<3G3_5+q5-4(6Mmh zS-nVAT#li`pb7`i=vsqzDw5eiSbjJFf%ufopT2umo1J(M+@qMTxfT55bg<(GEglB( z^i)s zH^#n0L3{`w7{cttcwd>mXtN%wgFF5JEnF}XBps=Za4MjXVO8-kfrHiF==pjuAPrUX zN-qv^d@!ogSSHX4`GZP?{Fcbe;+Aj5D*9`~{YtAagNB@3!N;W%jJPO6+U!Cr9QQX* zvGkI4#g94x-vRw+z1$rKXrRL82u%qH~|A3{jX*l2>}FfwR6J^fUvntPiZ`3GH) zzt5gjJ>)osQBp;5Xs&eDd&l=K>lSF*kUO}*e>Eo_TONQdH8A-R({?3@pDfvLH{_8<$c zU#rp>>X33r%m|f(Yn02Y@Joi6Vm7~xCMnL`2=O~RMUt&0zGqEWU~C_;t4{H)eI6dp zWWav;&D&qpbk~H?b+w?~Q|v%96yFAtkHQU$5a}A+jW=k372BXkj~a{C#W7+suZu*L zZriR*Xiz)B%ytH|=srbKX0@Q2O3x_ox+CmSt9Z?9D7!vu->&~wNM_q0fzD@=40Os% zq+A}YIL#C(d5dXhK;Qns>M_+Ig}LpHO}!Qa+&KQkWcx73aIBs6mn>cSY!C?jUMFe8 zt6z@;&&@dpxYCX#QUo8^uYX~Hv&q_4fF6f+3*^NEy_~=FUX8$(+!gN%!C@)F&kp4E zXO~yJ+DAlC46E2mYa)opyPjU1W zRmwd*jc|FC9dGxCn4hMO9v;0Gl0f9hg!j?wK@AAUR}`mt$FN~#jLT9PD-a#(MTG@+ zVc@A=fV`=q$WJF+3tbNwcXPWTNF$wNCCJZ<_LGCS3zb-)7vXBBxQ60idn2p}k^X+m zz>x3$=G)=ZbMwv-JSLZl3s4t}gO%6<>;4i7zH^IrmQkP&QTBtvpLG92LN>AMY)2z@ zj5eJ^)mUdtOt+Fgnn0J3w@WFFqaCDrQ6m1Zyo7vR7tpTn`$xj88GssB_Wl_N>N`uY zcK~@2#BvMyU6EdnmR@re{S}$Rzo{ai17BCapPR)F!H?P^FHU<$D1CM%spWpss$;tN z6WPeW5v7{6Bd6-*vyQr9a_tt{BpvIqUQ!NYSB9U4_>ShipAedXu7C><9QR&bqP5!y z_iP)85A$G9S3uC^TMud}0gnS;%Uaf%$h}XhwuvcOkAHL#vs7jR6`+$kb#P?P@{64vk6EftJ8EX?e@N7y_qE8u9R~@&Hnd;ZRmaI*7II#@x)&Vk zuizM*GbqDXKuU9{k8mj^aC_w5RS@Xp%7IW692TJ^p{xbu54QiVr3dwPWZ0e=1MOg4 zZEJyCT%h=Gw_X0=H#Vs@ndG8Boxnva~aV5Yci$;CYNqb$D#g_QH||;JN~K zD$@$qPS(dc*CIjKeYYOu2OvYOu4^7q_Cvv~)z8NY=mZPap1_EeTX^&gXsDYE#y>RP ztf8xMJJ)u|0+z(>%@G5=Q85+bcSS8US11sU#l|KB>0X2*fU&14(9-^hG5kRG^lKSl zBI?xA4R1)i57x;Oeaml3drmbUm4an)Z+J9Zn$|N>{+l89JY`*TE>-;rr?p+x$H|)t zb0Jf&MKeP+jON#WNVQ6t3sd}I$wx%LCx%A4$S)P(REk!WXM^*JC*El;Ut;RH=RSrO z6wQ9R0G{T&_Y4U(RMnNx6&(dPe9x0NWp&wXqOa7xB4J!tyL$1tyodB_LA9>h_?!Ok z_S%rqjwbTH8iKP*?)F#gL@0;~A3!#~&Citf-YL$-_-cJfA5tGR8J^;R0Gx(fS;#Uxe z;p#-uOctR5P7$Q;C?nw!oKVE@lGCxJ6{w1_Z%A>;Y(_@(qvX6q1VyA=9sGy1szN9w zZqyIsADIaFpp$hN{N~$&Z#*zqE8kn>WjR8=1Tm0SP&jV?n zez88bF+rLl8eD|3^e>6iPxj}DkoC$gvFILJa(W=`^oWpJ`Nofo6@KXo9-C3b#^+?T zndKdIUf*QmIuRos&&afu<#jz13WCXGh?^R9s``e`Zb4TZZAKCElK>*z%ibXw?r=Eo82lJ-7&*Td7a>ej^A@D7#l1c=uEc;k8@S?t6Zt`5r>lFoGiKBpBm*hc`_S7gW=Y8u z_O!*FIk!?l;h*> z5_aZ<5Jl*lT30%~R3bt#9u0;IsFOYpS*5Pka$d!A<~|)I?^}5f%bv9cj$s`ViJO42 z@2xRG{Ej6bbv0UPyW1%vtrMB%aOys-jW=Ntkk0>*Oc%djHY&w(F;L|kh7|-*H=N&g`;n#DXC6(XVfr zW|wElzhMqT5yq6xEd-z^Q_x^c-14Wj#7%W3@C-i?roon&+wN6qWV$nky4|1#RsCoq z`q@dCp8?|6udTl|lj`%HAAj+j&_o$br9>o<{~`VRN`KCY$u;+$^xk&4+PtkD7-KZ9 z@&AdXnl6z{&9*N^nul7gxgKpy9a)KIH^bgG5K4!|BGU_E8UG49qLXAu3#5IQ*=u zjHi}w9xcmEUuU_{z3U)Gu~0xJf>sZp0!bI$|p_)>kskH7b!-6T&R?*Drjz zV221Hw04jB*L=5bYz=0=naf1RK3 zYA=>#up*1HGEGQsEix^a8f+tlNqTg(z)J`Ojp8LYUE-!|)Ee^Sw9v+v7zLH8Q?VnB z$F=XUPy0$uOM#Y!C_cS;yIy=*Hq39Rh{omj?7$p7t*A#6(Sn8v-lPYjWcASKlg8zkfpnbZW6|Ig>8oq83xhK?6RrY z_2Yc76A^aZ_6ggJf?de_J?k}zW0~I!{YF}Jw?jrsK~xpA;Yyfn(?q%5C^ABw!`~*J z^a53R;Ce{@$xx@RkU`wZ#KJ0m2UkW565aDqg;HZuwMH3_=Yn#gf30Lz=2f2g{rY;r zb+3%ctRQ&RByGt9zqkMfEvva&52eR$6zWE`nV+1+4%rK0yDOFY|Lx?c1b zQqNU&@GgeYAF2)V4i$11RBua@pc8XmXst;VI#oR>vHdmiio?;?9^7*!>OSgsyGEI)|9Q=<%?SAh33u9@ zrk=oQTH`$pC;qju;#kedlSd`%{*bpUtqE_{4$B0yiVhx>UwYMuBw`5=j=`>NSBV2D z{}IghtY31*k*%R?KP+bFxDwX<%YVU7r0m(jmJISVF{B|5uU$}GcP~q&jC)6inpjzx z&Q@2-QF}xASE=X2Y-7S7!54P|4BApp+GTi!aq;Y~)im~>G6=_sGxzDq?8)6qTei)a zFZeZopx<6otHA5z;yH1!1Wbez+Yj}ExWpEI&!7JOM~y8IJk3O)NS(&n5Mt%#d-oGG z7X4-URAY7ZNh!S_6kud~@B#9nSkT0Rbu8m?H&`V3FH&GxtWxm`99BNc|8B#QH1!_^ z9KMdXa{L$=-g#<{A64rD?y+%(jmHIHxD3PzT9I?>_isBY=;00*kSiJCo{T7ajk$RL zc_GQI_o4nfZFf2N03=kTKl@OWdYF9t4@sLsFf$`WX6Gi7KA_1@@X0q1Ov_OxFis7i z#pO*X5Z!K*<{-&|HWihL?JCkI>Tr%KH zP3;m#eIR_U@Q3iGay8s;+mk(eaF{pB(o8q2_T0)WO)u8+zyWbv)GJ6>r6q`CR?gPP zS>-5j`nq&Z{_2&Cu;CS1tr!H~@h@HD=9tWLL2nQW#%>Bn-E!tG!3oy7-f;0X!i!Lx z1DQVqw1Hc2IH~rX+{Mg61@yW-)5j40y{9iZq-|HtnW>=kT^@-v{xDwgwS>KiQq->Be@M<9&nTDrWEBVDqQYn63UK|fCay~Iqfru!dM4U^wI@~g z7UhIky6sybrCrkYH<65R;p>t@pmV|Zk1t-2|wrE#NCagzkF@V zSoqKMnnEAju6sWQLEgT%NtbEHi*f_Sl1=~NNXlVKIm3I+8-yXnuv6eEqwFGSVYlj^ z;vVChRQGDK5Q4|tCp{^w8ViQ45K11077S`(-chl%V;k$bonI0Bx)j%myq2^MFVZ~c zV~56>$@#@YZK=oHKwLW6Ov%}0afHu|Qx=IAC0K4Rqe(6tR;QWkk29lq%z0&Imy4p$ z)sAYHLcKYpX)9dlA=|r2!kU$9IGSGDLAoT5G74K)W(_;GNg^YXt+AC%2tvExnud%y zSdREpOgKk)5RY?FZuWch-- z9FxyX6X9VjMm)>RJ=4z99Xb`SRVSHy0Ur)|1p(dkPaoFawYj1(VzCk($Z2+!RiZ8d zfL4>^%NTzPF^S(04Wg6#wTAk9m`EKhVIev68dNGM>lC^>LKB3?&Us4{Ybld_N*+gh zuT*-aX>@7?X;BjVNyxWG7z!AtO^8>cnbL5jC?8^+&>4&(JUXfWVce8no91SaosmAy zu~2^I4mMtT4AQ(VtHh7vvU*qj>Q&N-hL!D+$n6UI7O9PQxMU&ryiKRvZmG3INZvel zCWCH4%Lw0bvixAO`<8BM3kfw0B`uU3bIkCPIPDmF`I^NR2F}&%{UYUTpB_Z@R)d{I zw_R4M?sxpjuMYFqMCbhU1H(Yq)g3dXi{*h<@UcfXLz(PUKNYo?y~x;aCl;-9yTdF- zFXJ88-}kF0K|r@#+D?_m^q2;M7rVQ>HCA?2!+$<7cUU!fX>+G;6k9!uKz4L-#r$Y0 zOS!ggI!Ha}A2s&>$n`IEa>};DJ6K>-P12 z$;_wdZB8$?Hg1q&5&aK|2ggQSW<5rk=y${MH6+|-G{8^L-?sb54w7=9#s^O|I|Iv_ zJ>}O_IctdlK1x_}%%j(`a?s48jq#!Qx%P^ymao zHs!9XZZiQ@e-I>`6pLhnJvVBN_Fjs1Di1f?H=weD2qyY0lZ+(4p`i?gZA z|B$|6roUFyGpo{2nan_Q+qO(Y^43zPYfTh2iY>5+3ELd@wN87gim~fxx0E!7b((Z% zFSRs)iUxXX{*{1Tve&ts0`&M0`XB4er;vcF<4d9KPH^17tio|W;epZ_4F`ACze?Dh2+Vg4swBKa94eTnAH%0RQBu)Y9bojJ+u0bj=WzwohCsycBRwta&S7Q1 zqxh9xr!M+S5{zpqV?drOR`9#B_HDW1)~OD_&vv#pw#*d!R!N){VAv{KNu(2&t+$>A zI>IZ^>Oi#gekFM*ruz@66<+M`U-2=+&u#qYThSNx(saq|%4cxZD`SvZUFC@}TmlBc_C{t0Zo|_RCT5vaq`T-CeZ<+( zkXV-hT;MFSjzd$)A0veLV^bRb!hIa}rM&8KkM`{LcF2~(;7MVs#iS`lmQV!ai4 zPM*+i{}@Zpd!v945EJ|8VILF-g?$YD)BuD^Ct~%U(f#(JGRlqX2P5k$OFmN)WV{=Q zl`2D}-%bWdYb&ri2!TA3M2U?EZ#w7{D$O+BZ=+c#Qf7s|@A}tMMw3g8g1s%tgEtTK zf%>!%-f2VTYAd3|nsxWG47;l4w}<8`)2H+o##y&hxvNJGLg&;@+8yb=N74PaS1L3E zszxauGUJe(e^rViS`#HyPn;>f0DR%bJJSWq@X(|H9Aa&kRv>J-C8xlRt!l5p!UQPg zP!DwoXLPZX>J7C2&HK4JGmvf4&I%c~zK)hO`Tl$yGMQ%+1r@k=v%A)`#F#M-WSf2KpxJA-X-&?`eut7MPpo~8g56SZzls@LZ6TajAk!#B}$@n#+9I;KyrPRA))0FL+lj;^Fk*4FP(~kRf@ClD?mo;bP_m{-H zD$S>AJr{~C&z*kFr%9<6jG9@{GxmH|tr2dD@ME)fq2tULcO?PDi{kRcLGJzMtKV}^ z95HjwlDjap`rrMCblN-d<#E;NZfNy)&UF3Hb+bUaU%J|MA?KO82?>qK^L6r#;DQq4 zZi&wwcZ?x*_8aJzWB#{dP|cRL_Vo@>2-KiYDS@#)_GnUd&c`cuIGyRcw^hhV~; z=7TGs*shd>iQz(ZARAz%$1d7#SH=J$sCgGuBXFDn*Py?$&$83M$!nG@u4e=Tp0~5y zALt(so1wj^n&xa6hD(MDef;Y^Jqn0C#o9RKDGvP{vR+3X$*Xz2eo}nMHDLac*4@;W zHSObRn5mH)mkw;@f(fNQH@dri?DDgd&=l-*iVHaWDH+2labuXYo2~iRP96EpL-RkI zcnqJVc9v3<`?Y|Ps!2a_7@nMBhe_kxWNfM$2i(ePO5(2t{g{GSW@FC6M}J;HkK|~A zf}g9DZpM9wvD8kFaO}>bzaq-2C#y_War6E|VrD52c&k4bWyI;$Fq&xK;IXtS<)cLO zbtk53gD%9OA8S}!+#J(a!tC?KQe8pZ z7Ow7Blk}*wlS869BiHn?N7DuSD!+bAhA^guBu&%7v&B)N=p~c@WfGF7rmIiK<*7*U$OVpR;hB4ZS4p;Pd6N41 z_@gR``V;Fc9OO=Wp9D03(v4Kv5;q=tduAsmI69q>kJPnu20u7A{xYL?J*yX-AG;MK z8DrknR_sLwjcND4pnJmq{Sb(?`!;>w#6kN{>3oOWO8y3GA;Fl?H;?|4dvM}cpOH{e zv?t8HMZ+JSiF9WtN-Q<3cX=bb6XthF?((%Ao{T2SOBcn&s7k+56#u&GY2>jzlyrMb zhV4J-X(G!H8k`DzP2avPx8kupOi>UaR49Jg(qW>HQ2UwKSmSO{JqGk$(k}TmK7ijT z*G{){a;lVFGWsfpOK?0>xYrW@F24BX5tb^|?z3^#lKTXmL5#PAflv8u7kz}#l9%OP z`xi;M;jCU;JQv}OoJzNET;_4&Bj#N&rPQjLEA z6{quSUrl^kE*(pHSY7H`fo0@&S{yb0G5EO9$AX*evOK-9grn*vsH&x8Yz1>C1FFj2 zc&$|9M0D<&I(>fvRSM5Tv)Cb0L>R89g9#o;F7s~YII z$XvoE33t!_QhDn}JWD~0o%^|Bk22Io*{FmSY^D~VuS8o|u^V4JD!h|;mk6q?E28|t zqM1P!i+cyA)0y-v9=g_;ft{Qnm!6_73nU32v$Cq80VPu|BJ6LvGp)ERgTG=6gyW#V zY{pQGIS7EgRYXRmceI3%P$!&U5SvY&MBjO@4fbn~lBdprJ&w*5^Tkgm?C8?jsCKq2G{ z38=89BI9(H=c@4ZM<0w>)T2k}7~6ZJ_;Jo?RwLA^r-Qo<%Utt5TZ8>FUV7LHke7K8 zN8VIg_D1k!^sECtzQA+1(gt0Nbc<3ly!G zuuwgKLBGr@nKbPF5NxpBn|1j%kB@dEIb{F3WES_!%rzKw$6PW98VucC#f0OZ05kOu z8KQOM$SGv0tAUVog0pQHdBobtdCKR7fA|KV7m<1${zI@653Xm3gWyTn870 zk3fGE3Y@DBeKgxd)2Ev#P$s^R#qI9IO7`swaY1W)80|ZPXg$=;O#S^{$*F8=$JAZ= zIj50~p6O9!T2v>&Dpc7Att25G5jF2t0N+H0b-*<3LIHNB$G3{#+kpF|`6cVR{!xCe z<0dYgC}dEXX7e`8_E<*%r9)0CK)Hm(z~E)q``&V%V`9vmhzZv~0>0i3Z4)~RdCCdd<4MDgD@%-TH5+5RY(H+Op#o>GhrhLg4?O1DO6nAskyty_ zARls-%9G-%kCce{qhkKx0Lv?ERFCwZIb)!pDv_25cc#p%F-QmHGrJ31{6C}$+K}1P zz-hekfnP^0rc#|EzGsHW>x8SdD#`K$r?GyLouY+JN+9p}f9@IyZ#1P8=ipLHzwOA| zc|8JF3hub_QO@pJY8aG4_Y7PM%lOA&XB|XC9XpL1Qkh^}mCPqv^`}YuH?AK$E4G>a zsQ|>(yaf|%g9%_jbaRZjE_2UVFz7#G>x*Ss=%&O37PSmUt#P@)7E6^HhyA>?0v^6A z*ZxZX!tcy&o)o&feZVR0yrr3AE4deaRut{)450S&7b`izJew;1;WeL9jzknOJ0IsE z?E>%3a2?{o@%>Kb{?K%WSdo4hJU!_QMwwwftn}R|H6QaSM%FEQ&|NT$W}rmbv?dvC zS`C^PS+7H2tbDa)2r{!w{P4${=@E6v6qkIQDnX)};9Mu2M`mzH!OeICB{nf_)}R|4 zQqH<4q@k)>FQNj+19eYvI7+zimnaO6@)gLF)jW?_eGJU(cYqU*A`foEa91tvRdh>O1W#?5|47tuZyeKC-d9$ zt=xkFu%vef>_bBmqiH4c31Ic#;klFko;8PVBjtPiJSs0A40SOQ1Wg@5o11=3tdznY z0T*J1Vm=oyahqLF%H|&A#Rjx_^6$c1oOy5sbb7j%ei*RvrQYr=q{Mt}zbBV5@K*iI zKU{f9!k*sO%w1-T@qD90^ui0t^BBuqnLH|;pWHrU+>OQxf37+mF8#jBYs$^Dw;L(z z$vXDyfTQF?`1mKmjEwX01`#Y&Vd?5Pg5`iM(e|F&gU^vab;dTFLhSUv{($?L2Xmo^~*kQzB!a`fl&){rb1e75TQ4V!$j*Gw_e$ABc{`j|Uh)Q@$@to%^ud>!yfOn8R z{IjfcDozPFmzIlm*95sYw*7DZt-?ZtCaddH-{Q1YX3bP-2aYe&< zL$Aq7^}CaTF_}=?uJ|DzQmz7QuGq~BbdtZP+S$z@6~2>_zzF0hCdCam%U0I>3kk+c z*?Kc3n0?Z(t2brV7{ms^(mw)I(m80)m#+-IZKeL<__{d%TIx9Ic{AF558A_D|cY)b$K6A(?|HZ%$ zm!9dEKGd(*7W=~0OoEoZz4GCKGfA>zNM<~P?##(+L>xg(l%nc>l|^Xl-X40c+g}(I zAu>y?B!aMsU3&F;O43LPfO)30Of$Lkr@r-%m~`olni*6{%ln1C#H4?s@%6fY5}aKW z9fg1DlFPe&DN2-RS(Z4?dW%Ck0?pzS+WzEdi;G}%g}7PgxGn2AQnq8W&LRg%E@ zZBLfd-wKkfr?j9HYo+#iHCY#zg=w8h7uk#;yP<_yJhL8!S zud+zWFwhDh1uf*~M2meZl+ZLuP>rSTFpbvx>StRpT;#v>nlrn%FsQ}Mn}_uE8&V^e z0v5$%{kw>7qhH`Y)7jb>!JQUBw=u+irHqj1{Znu#MM7#PqMQ{VRo(T)1YG=M@&z_l z5=5_03RsFBBTvSo6plaBUB})MqwxB?=~SybFEx{A6eKNWM&_78m(68>-OF~ zc}fDJGMyv4$Y`0|uOYG(O!pM`L{aMccl{IH&v#blYk_xkFu=I2`L}wxOgYZ`m?Q2@ z+-G6Fwf(h)Hwz{vduM_r?Ty%XnU_ifyQQ+ZRB?Jtr_4UN_oJ_hyXRe86;^W|miaMD z?1T7b3rc00UHB82kOLb6!w#;PV{ZYRUm{`?Y-_clR&~#f<1}ze@Is+SvQ~h zO8FJZw^4VmM$eymX5;GP-bewfa5@udkA7!7&3xGI5XV?Q^`F5XQ$y3oQsF34olmMP zkgooMv0|U(=xY}qru9X)zb#Hq&hznj>X1Gsc7@bJ>OGm-=GIQ z4Vpjqe~lTP5BZv-kZw?y9E8)s>J%m{dIZW7k!ahz-J$jBe?I=I{%VMEUoVzEBxNRf zl$<)Cn&E7cdlx^TYH7#Hh1y|mn>+f;KD7x0e&gqLvK`u!KEW);NNf-Thj6fQbJN<> zJM!W&b4erMB)k%DtLZSmyD%k6UP3=}Igh4r`G-nxl2NAB&&__jb174lKtlCG`XDjX zHh6KlX#al`TYgUAc$Ja?yR%16V*x5{xV3oAXsF8rHL`i%6ttn5V`Q@Nn9H>zJnVL&IlQ#SY1+c)>Mws1jiCTk1TIFtcpGyLE)_kvo#`bB|xk8WWs> zh1t^?z%Jr{JwgiA0;pE5QiYPxGODcVIv4 z5s~ozjf{|xfr4V53ncjST0K#IK-X2q!k@=e!9!+ay1j(PNCv zfhe=`4WLjpkRA&KreCkZR}`Q9`6LTNrNXbBf9~)}W>4RgzbOWJCjP@KBt^X5ra9?& zvBxH`^(0gL9gc+ZhcKuHg0JzU`c`RsJ^cMRV}Yn-NGjP8*wtHG3e(jZHdcdC!H{zo zo%M3^gs=L;b4)5Q2p)}6$sn@0E?tODuuc?K=|K7gmh2-{*=I|$b{Kdr-vJj^B;WD&L0_vp`uB==gJ z;a%)kr&Ac~H&Y*U)pufe>9i4tXmG!??fp1p#0+sx=_`k7Jrfqup^5NsoJacY1r%2(zeke= z*vnGcj>o2RV9F_L8{fMd8_m!!R78UD91|+0BIdH6|AFDRcqymY2M_6cL|IsSJbB=h3p*ChZ(2(dl9C7*v=d-N5T!FH1jIj{sddh z)tuM4=--}ZV|kxO`hxZ{C&_+^1CwYgctqqpU~Nv}Rl!B)mk>~)l)y`b5uN@9bDWhg zq`HHyF5GVld05X)%Pu7H^BTJ|X?82iZT*z*0nA;&g9C_Ais0niE%zEES3Hyomzvsspg`FPpShrFZ{w4e*?Eq?T~oZRh|76}%mir;vCp#IaRsSq zk+NPHc9jLJ#Dt||r00O7<>@-lAt7sR9W5(1q=ctcy`@=hnVt~$!qI3=b;4yDV5Z3BJat+%=wriDDX7{f zCyl*;njRv|*b`Q;=K!t|hj6`dkwB>jb_&piJ0jb8x9#=qw zLyLjj_P*!ojQ-Lo%I%ATiokw)$%v|rP`W$LTK2LfY(3YqDvwwVEG&<)1}^&@dtM-Et|;WK(eelry=dz#DQRU zxR&@L>+|D$Nprg74UorDo~`P}jP^tkUu%<@DvW$X%uIm(CT|LdzOy}UJ0zg0bM`cC z&osjG0N8%#Cz~iD>cpQ;S%PTqCimYEf)`7TV*Rg{P>;1s8p`!iuZUS!!L)9+2kbvGY#tRls?Pl!a^-1JyD6%fA3J_#qR!}>sO8~T-6M;! z-6uahH|yu0?u-!fY|4YS^mMFPBcIds!|Xr5m|<_6=4y|d?{)ma$`>xE+t#T};LErI zoXQ24B}T2U4$&MWu60V|R|78bfCcF9Li%DIrK$PxCyrU@!w&7`+0s3oe)wz|=xWjR zDF$6h(AwpbG3C)*3&$1Teh2w42Gm2~FzNN6Yi959mDYeOhk9FAD%kD&xHAidW1FJ_ zURr(6x-bvrGtZe7CRDCu#j_I*3s0BaH!}rFKS3h~oej;PZ*=!e5EC%O($t-+!FJZ? zpI+x$>nm{U1S_TG+klUWSCef1%Mpw(D**j0$Y9Rr1MqrJ^iBUU0D!m{*JYwJzQhOo zE1ifOXYz!}J7)feq?F;!I{zP1sZr>_qXV9mBI8Uihp|^T^)6$+p{vqg&@?ga`yR_3 z)MMCn`NRwQSXZXp2a^1r+NR_q4|~SBo5Ro6U74VhVfW`s?iNR@UoF--FMyK*vlnHc z8ytmOSm{*Fvp`8Rd;PlYMS(+>30$36s+WGY$snYCE0pbJU7?O>+7qG)iA9GCmM?r+ z70<3K!k86b(4UUm`owiwnzP1&vFDI^r&f|H>WLUbew>R`>d{A6dDqDBs<((xbkN-y%g_ZW-RR5Qq zl@`zB`+3uv3hf5d(zgfwAJ)AA&WzOiz&UKn4ok8tK2s4fk7V+ zI4}*@p!X~?UDbg=D*8>~BAy5#$(Ik(sRH;%j6)uOO^2QQf=O4F*j|(>w_7_Q9X5c6 zhJkiguv2l8uvpg$w7tQ|_%(^6XCZSD=V~~)ywbX+pwVG`Y?YX%Sq0-TMxnVcklAkO zkt^(PtY7#aJ8JSJ#O;>lhx0V>RXKNUc(-MM$}p|@A1Ew9RB@6^oA3O`=nFr4vY%Ej zxUC%n$2q2d4nkDWULKhZop99q(e@qn*I5SNTF~j}l-ra+# z5Fs_uEM6~bhHw1|ctIwULz@Pl1yy~7Gk!N(L-91db%v{}q)cDkss9*s2Mg&J&rx?Y zBt2yi6Ez;Kh0p$c>;!G;Uo*EjhQoiq_UcUX;U_?s&qMVVkMcZ27D{OA>TbkEv(Nv9 zHlGfE_gZWsM)N!B1E%-K!QCA@zb*!k488`c=PDd&mQDiURdo^r*d@9?ZLPL5#X$#- zm~8_&Xe?i_`gi}k;7!+mzpbdoi&#H?I+M+4@y*|`+R0UYun!w`x1mezi1$j^Wxh(0mi8>q5c$mvi}c0Ym3!1BmeOFz`}aC#;FlWYNByB$=bT+e zni@wfVv%bX(PwmxhW@p1w8!*G)l|;RJuo$)Dif)mhgd4K+u6E&6;dgudxV5&6<_Ez z9}uX2>L5;c|31l>L7*n_JfZD9WLFolnZ)`>Sn+9dOS&}#IU`2+aY4VdqJ>UX@Emcy z|6ovKC|uR?hP87=U8ADahC=}NR;fzN!v1uBThbx)a0q3O^(h(H^y#4Tc(&@>qyGX!6Kz^At0H0>HahEyv%_uO$aDj34rm8D+-T7cthz@r7M;^2W zfNvrP3ag){X{i~xeBgoXD)K=YMJ7C7DK}4@u>XAB<=tYKq--|&$PRrrfO}72hE1|q{7{bGXpL{ z44+)pV`DQMnN=74>b-LB8h+^j1HMIIT-;5+laI!RL&0AkD*^^R#5vm8FH6NTl0|B|Qrq{*QT{<0lTMVFTO@*EgoxX#RP=i&vICq;Cz889#{0t$BQ7qu3Q$H8|$&vNQzM z)K+#T4YU(@YT81~*RzJ?W{{RHJedB9QK2}C)W&{3NA|2I>aq%T4 zn`NGzb0ZP*hK%rk3X4$jp7&@p%{ptjdS_uE`{t^~-Z0dFG3h4NG07Z$Jk>@D!kt@b zb@`oDwZ^M`r!1F|e`b%pnL@W8OypJ`wWAF|M4#IBBp!v3m7?@w)=Ovk7~ow1Rk)omqU zK4@*hALCsniQ-FbGf)2jiFS!`EcgMWbKl#FqW=I9BD6-f(sX-!cw0Yta>vm0IjXYw z%U`~Vb&UibZVNJlz#g1dtxjsq86^n2roS^%@8Tx4a9LrtNO{TMlaJ;3S37%id#n|b z?)q1V{M!+6$D#D5tT!{cneLi$es&!IKc!elV!|K{GJg?a<*EMwc=a`;X~ru;Ct6a{ z<$HK7?eHz_%eg#;Bq{xQHD%kA4 z9oRpQO5L^aUG$MG`=Pc@No%TaY_9Kb5uyr zmpD;u&OMIr<%%S0k6$bg&YonNQgIWUbz*k@w8+pjyBa_U^ z2;8zU9D7#v&x@>$zmsR-lvE&vBr9OxW1#7tz3Ut-o3Pjjy&R3dkTk@fF2$r`NARe@ z`tos#iPKUvgxq=wd#c^v?E^@(wazn{2pPw2nEdJm^O<>+BJ?2!N9WB>_W-Fb$sy&PTby2y{o_5A~s*2@A$+Za+%17OAUR#>*bFExb~7Q2zig z;hL3nd+W#8wig8t-76l@2h`N7;Qc@nR>%>MK6md^CkLd0GI4(CN?l)5mpiq4iMIk6 z2-JWNTya@)h$Oj^c_K5+$0kWzlk~-O*FG53tyxpagvi*!tKj`AlIdEoG6uI-D8*#m zkCcwQ=9Cm8v2&G4d|Mex#|IIqKkSN^?9&AgAXQV)Gm5+YhpGYl*Z?@)^1AK)deuqv z`0b=3=`J^SJ9y9GTJv1bmqH+c^Or?I)T?trOE)D-yra-K{Q^gDrpJua+66a!Vx_ys0;yirB~J z(x+PyAI!oP9`Aq$;Zqf6IZFGL#Bk^3EQ9f_{RhL^Ua+J|tw$T<{ZU=7@15AJM;6M& z?jSFC+x%!RV&fBBN)tcD7(cBk-r73^Eq{Aswx7bD7qwT9?F}oc7ZSYVX5Vaet~XR| zA*Y@_I>vS?*p5#$6mCvQ@9EOEBDL^55i6UCZeJ^ucWBCk)PatF3a_Y-2*nO8#O%5I zu*k=cZ1k>ZO{sg${En$pO?$CM9CmNFE`d*{nV-`&D$4O^V&fm(K!2S?Ah(&zK?TQ^ zk<5+A{P9;I(=M(FOK2o)4E&3dE3Ws{<91HWtZ!y){Kx+QLH<2^0Y*o->&3qGbcA;*ifH{LGuMc z{>TQR-mHJ);f6nl?$n|I8Qh?sMJ>fC61OfBZ>G_TL8%Zk63UVY$@vZ3ezYe4V zTa9*Elpt^+d}McA6OsOLT)NxNMj0Hb^kYcYfq5|&KIW!zli7qE6ZetRU)%V0)BRoi zp+P@6mpi@GVB~%^&rfq|TNbm;Fzv=ar9tIkzExB{!lY8_FbOB>4G>aWvZp9LB7<~| zjHJMNyKz!&i4$~}FB$XzH}VvVG^j$BBp%J2ew5NCc@F`8fYq}l=1d|+>oc&&A17Kv z1VOUSs67gvYPok)h7?0*C#HWubi0y`| z;Z$&vBYgpo$@a|vPIm#%nV;^yN9#={LNFFZ`VweZu8Zy{Nc3~Z^Qi(}Gi(w? z5dQ#V=79`d!2I*Fk3zJM<+oQ+?m_9esJF!*K4Wk1gVuqP#)K}| zZ{t7g_&qdugc@U09$ z$!NR=C)ra!%+iT$m!xjT{h^QLNxi@4qMUjFO(Rasyu_m)-d?m^3DKaQP?1(A2kD#-n!%!z9poNYpKuI@dbW-?g;@;J+ZUv+f=dYMpjPc3G6+v`+xvl27wJ^^dMDG!}3%aj zf-;GFBx#Hk0AupTDW>O9nZXwTZ5u&Q7X$mF9lfg?*15ke71fEbqi)+YOna;2`5Hj; z{J>8;201^y?szocvXuZyc7zk3nT0$;u?&e-hpL>`sxj-impqrk^ef2-lLAjQhoN$4 zi6V?)ZN_u=iK`xBv9T}#75Vw*V-pl11y;g4Y5{{X_KRXIw9CpV#o<>RZUADsb7`?3Ze!ynGA z>UxHdUNfga>y|swl#mB(W~qyWkl@4z3{h0zCai3S5pMQ}r34q+I-_ zPCYP1JW8%G&Zix*gH$27Vf-Oawgosq&&x@H^%MgZ2?UeuL8jmn@~Z>tR)K6L$kDLV zui~`v9!6u4NcK3OMQZ2KC_f!w1 zO0da~nB(fi)S?XaB>gD61r$V#*5a3d}BKjBPRuB1G`pdasMrd#`iz_-BlrTb;c=@JjA z6kUKNtWo#^qn7I3pjuCQr2tEJQ8gi|)i&`O zQn@U?YklV<+id%u@=kW)U2)DOFezekk0kzy$(B_o= z{h6)J)$Sy0e_ux3i_iAMs4CB?+@vkc(%Q9``t}I^g~A=`O(;$ovZV_N%e76-&C?g z^WH|s(K$b*a*u40a`$l)>g2JaJDX%UYr=h^i2neMEs9B} z)%aU?KHI>ow79ayTGsMkcKJu@Y0a-e0NDe#`ccX*XV*cOGfyGNMo(?^Rppd$ zQ+K;R_|=i9U0pZJZqT9bu;l(^Q(0}v%UWDqNuF9v04lj`E@t$(6_AOf1^k2S^X~pt z(A&jks0#?cI>Kq)7ZBSde}-zC+uhzDnknD&#V%IIbhp&$F0O4rDH~fU_W;!-(k`y# z+}E*3A7LCqt6FF?0hNSCPdRgn)q=)bd~dsqA3zIK?bH>e(6eo!+r+;-tuuZCtp5OI zz=Ml*{{Vp#P$-dk`&mCl0)aCR`D-kP*=jE$`65oXAx>5}6Wm4#{b-uinQ-^4iaRL+ znvEm<;Q0RlXyTFMi>rS+E*3LxZLE+T-hA!pyZmb^WkBC0iDBr|llltZk?bNFc_Cl- zuxgCA`gl``$@YyxkIt;m>>>&rSantiP6p5-#ere&2dn!hzr?}KDa(uY$ zv!A_jF@Mo&jJlpqf3!lN?F1VD{7q?pA~{)RHPxdZB)DH)pnoA+7N=0^Nn`434K=%E z1ijVNgmeIoYH{L8LH*1TkM^!-{(_~q*rgZT=>FGn3X>{;J$DL-YPrtac5iT`{{TD+ z=VtL$&>IcivpN0?c(eMGOoer8@?J(rqwD+9dHe<_Q0@)b=uW)pkhW5j^a6-ArgzM- z6zBMjXTzsRss8{UFt4dh5Amw^X=Mn^+N7nwy9oZcp~gJ}nny}?`)G$MN4QMYX}m>i zC*6)|52p$%hc_a5$h5b*nDpHFZ|hnS+TZzQrALK-1g5E^FKOg%Nv{~xvf4zW(WESY zAZs?`UDc&OxVN=L^(Y7GO=Zmmu_ttFgEcL+jj$P%T*%&yDaYh7SC+{~FJ%wkX zrlkn(EVG#8iJQ3ySpI^YEt6iS&2czR!7{(ftqYB$o zGD2sz<5I1>j52UmqiEqq{{UAJZ+>VC5T>BcNQ`Iw)HIR{xkl`{9)yLd!aFuhs~Y+$ z{{W3WzRIKKndCoT@S@?lLN$?C?_^)A6H>R@BwR%*k8v3_YBhwuV^9yNHDKGB1~)hS z28-M*WeBB+?zp}`Kmyf>l_mSEWcTY<*Gsfl4Ds)A-MOnkM-T!y$IuK?xLATTnFq?D zN2wJ$dAa%2wM?7XV*mrIZ{I7UDO_vurZ)iViaI{)!7o;=vihG46`@kO!5?rU~NC% zHAr0o`$Mirtz1Z^Wew&=2f3u~hq!{yB2OeA_C*31;{dBW5$e?q+P~dvl>;4A=CV!9 zR>xtwA6iMtkCrC9*YR8Nx2LOCbo$(du^O#FF0}9i8Jb2v-Xscwd0@GT#8I*A2+lt` zrP+!37UR`f{#jWR`Wmoq;qt*AP~)%4Yt1#C8s=FHiK|C#f&6Ia0;00;^sg8P#WO26 z_fRPO0U*`N5lZQhr+wAV(rroYrE@*?x<;IpRT;%*U3l+Fw{zs}67&zfe>_(;sCXc! z@2A!c!0Y?N*Y&LZJ`W7qT!g=h?U{OzgdzO8)8tmY!`fZhNAEk>{w7&lDy!d#k6@@z z@~zu#O?A6;n*qTYRuPa#<|~l5@lS=Uwk@TkASmV5)V4n{T{exajd9taX-&GGS(9k` z4_fDvSJ|4!p4!;ac9tlB6E;BX-_o$I?@KdA!PZXCu}(et;MT?Elzw0DX2B;5x&BuC z0Io;G5%_Ky0KdJK79CbMAh*)4n~m%#DL#g(TiEIrKWDy0om6AZV5(2xqO$EYp9x#r zyzgmh6;3xZwm>&D(7y<_BTwnZ4-!x8xRtVmYlC=8@4auOkuN7AN}%ooGnlPKgyGD>}NFe&>u zp!TzSY4RC!t7?~tviDJzb;sOOfPJ{@Ro2ngRmHI9{h|r2OJgEH%WDiWv2IX4`)d^jIy2k_N}>7^da?Nfg(*Q?woL?BhB55zthz`O>%o zc5%_4KYQEL9`!2AYiS`dLLogdjo=?#^)$ukRR|?yWr|4fGljuFnX3ZYX#%v$g-6|A zFz4|ZspGl??YCEP{999key621_7*KKlz^UhvCsMXRVx#yixi^)+$cR1FJH&KK2VJc znQmm*cv#qAeqHE}&`9OtljQ?DkQL+l)J(|<{nrjpErXxdjR&awhKw(r5FL^@{$usx zfRDs!_J~{!s;@I+AIJ&_nBbL-(Yo&WkLTKh1>#0bR<6*VPEeHm>RL}m5tEM2#;%*B zKD7#ob=aVW+A|Y*v;4}wnB$MeuiSW7L(*Y%I-2>)p@VH-n4Y7l&wAu0GO~n{1yuD5 zwEqD5svoo9hA~0UPcV`zKW8PgvNiUQe-^LQzkM~OmD@#aa&8dg20grrh+Lwb%?{#w zvw{4EYAE1{ox7wM>SF_M>r$Ir5(~)ENW-RE?^f$$2KCWcS#Fr9-7FzbNWdc><4}cO z8a0%^s3m{KtR~xhHMOxuJaV}s^ECNpl?EMzl0We01M?IEZIO9KRXI0uH+&qg>q@He z0F5f!kNRv>=kTcHblZtcrcUYs9DRG#Nhg<+41~yf<|0U-Wsw!Sp;Wkxxj8IQWAp1* zw5!|QDsgdWwv&ilaI65Xinsy8|Rnj z58UzF7_7v9B{Jd|S|vI1&L% z&}&j=ONb)gZIng^cW+(~zokT%6FJ<&&GbZE{-U1?>5;O%#krglf~cW!`qrGcz8ytm zi%7mw7x!dLqn~gpd75@*s9$3pu0v&{gr__L4Mr{$lFzg^Y%WDvEewu)*dmB_3nL$= zts=U!AGrY^aD^;QUCiE*an!<1G)dMQ8CrFFVL#3(E*1P4pc7$O!>8{Z`6xv>jQ zF}~(==^S4#=qjtnX&i?(?~&Qmr}M1m1wG9nK1X|8>E`%{JJ>Qu8~va+$j*ZVdJrp? zlKWP^m8LfmM1`_^qq7F3V{F)1z>JQ&fzRhrxmJ;O#g)zrx#RVsoftz>s#mE!HWd_d zOGzX3EKdCXw2IK7C(A&6b`ev3lYC=hNCEX2&(gHC{{RZ#U$H3(^835ERUcf|jisrB zPh%uc8v+D-i0kE$N99Tp_Ia-qyE|@7kLO#G_-MsyvWuH^iP)%dD8@&*7!}VZ*~@75|KX!n2EO2Q;0mO5lW7zfmDW(}2 z{&^Jt0J;;mRTeD78Fz+-OhJ@T`6L)GRP3@&s^>~rPw2GBn&zp z2l!GN6=NuJ4^iCYe>!u?aGBc@74=uB3UzNHM;YoH{3uCoRY;Bm3+^&$fWBnS);Tlv z{J04ku4;ZOens-?G&QN58NgMM3}-~&qb6GUobT<>mrVaO-- zr!yeIB0zm|F~d;=Bbv~tI&#^uSui3>0DsP?e# zQiS^)vq+fvUT?*R=}{%+>?M-g>S)#ZV3HLC``2Or00>kTl6gm1iC-D{k$L>jAB|VI z@Z534WnD@cR5sIuBPX!WPs)?2PFgbK8nMO(VD_0ENPZ6?s@Bo` zn_oVd&-13KEiN7xMD_DPHElpu+9fPc@CN7et1==o+p2(Mnl3Ob5|_#(?uKExb(MeV0Cu{{YsjiQ(N+<}BNZULnp;PtK{>-&|?4#^}+{j)=#P z;^wkNL6-^^?>_5S+u*+qIk{3cO+!)MF1aRobnB7*w5j6h+}!>k`bSrG>y1=V*}~= zRx+y??rRz|lF-6f_MDja1L#V(=TpIQ_U<>@tkL&bAN&Pdxsyv&K31!-Onc%7Et8+R zK^z|ShxUeM%E+XDc=1;mDXj^d9Y2WEyVawSV@Ncpqg}r?T<1UD1Rv*BHkSd2WzTHx z6vdnd!_2)ufxr6H({knVj8D`9{VB#a6r|)%6b`IMu*jf=1P#aj1(Q(;Wjq#G{{Siq zzwt_>b>kI8bS4hC!I8)4C=NRLb-c)aLX;|AHfCKEb4G;6JLCHo_=tuRX z5+fYMtUb38RoSi7jO;n+H(=8YiZg%|4|++1MYeZ52aylvKT3(xNe0)s+&vh7jYbJz zGD!!~t47O3y1ygHjIxvbTz@_&Z6&C#Q~MOn8AXwdXZS%Renin64-ncn5$T-%mAw8A z)FF&Fnya2l2|+9M#bU{%&lK`PBapg{&N>i1Po+YYJLqf6HyuDUN^r>;KIDo~BtxW8 zufBIxQyPK5IO&pe{HZp_)Dxffj}@iN?2(~ws{MlMDPy;{n`30D;58T8{l(U-ZW75B zCm1&4fuB%CXMyMtpFn9zd~*=RLxOJQ!frbg{h~>~*G7>yyAgr-(_3j6+z-0FKvCPG zWBu-wq6h6C=~R`7db^VCh##I&_?kb_yC^5rIR5|&K^axfQ$CahT=Rn8?5O^<#cPY_ zMxzQ!eK2WN^T?1Ns2|dmcY(lGUq%9g$T@3#d#Io+AaYMQ9{8t|Z;Z5@`w}WMDt3tD z)RRpmTcYBC*qtt6A1IKh^kLSW_PmX%6pyH?{#ZZ2qu!K8!=R580VsHh%fDZBHzW-;F*vY~eW&#y!Z*Jqf!qTVK@WIEK|Z{_JF$ktWyWJDdbx zO@LObYF5yUXYf&qr!KA}IE^#)ro92T+=Zsr2OE%kk}5LVqaWT~TSPs%H9E-Ca=;J3 z)tId!Jg`&vQ1k@ZS{TCfHRzE40J61qKQH)0x4tUeR*--=1L!Ks+r$U|0A8ZySfPSP z-N)8|cJ?IuR)*NuqKb-3jh}QVqTyl%E`Q~Vk@Tc_r(A@INBhRB+}}z8!CJ?kQM%uR zmNE2Pnxz)R%D?aJ{z9!mWoCmjKK38qke};P)9N*5 zTq47E7AV;N0JPbuqey_^5l8zg{{YseM=hd%s)6#oFg zu~gedzL%D|U=L*!gTqY6_mh4k)3kbt$@D`{h+Xo&Y$!c}CXnhX<1KREfW=*lNVT^a z#_~O?ezrbQ(79XK$7_HFSmW7O1M@W*yo2uU!TjrI`!3OWl(dJ{j(>l!f_4M%{{X%YD(qTRGGM%ssPqaytwMFEV>q|b*yH%6Wl{X|O?`gh zMtrMSNBi0T019`L*ew;gsG3figOrwL^a^oPCYfo}SYUs*f&FWqeOlt{)u(+5OHy3i}u`CDjq~iLH@$7WM!p%7_Td&lLWY=1Cn&)-8u>Ro5u2SN{@-Lg^ z1JK6If1O1Pw+Q=w%W&t_U{xQPG`Sg9k7IT9n4O1{Zqf|(@_-+iH5}UPu&2n@(EB@& z=qa$nJnB5fk|F-vkK_e9OD#QD_t#Ea(J*P~Cw_)x+Ww<4+Ly2a{=y&TMM9TXyfm{J zN$gpV=qkjq*jwe}j`~*Z(P|jAff2-7T*b52NwR-Rwd6;=)!38mT8aMvS8XBm*%p7D z4RPWUK1)la9;_8d>S<)w?!x~7$GSj2+mL^aW!+s~T`@j(@#w3OM=KrD>R$Ukp*J^r zkCFb#xf%SHrh+*k{{UBa=ATg6{&k&YV|yw2*J}Ef3H>W(+fIc@`$gOf{>)^%Qg#|` zlES*Q-*)a~U-mYC_3F^Ky-}I3WIy%N*e5oT8>R?QO4MY2lq%}{Dl^i8s%M1plNZa21|usWK~*tx4_#RsT%(Py_5P1 ze>((%5&op%6hC^5Xz{sps{ocOvFS zD6Irz)ubf;lz(R0%KreonpsEpRn9+3(1OMYf7Drp_hrHS%}$7BYT3k#o13{GAHs#P zQbhL1+imP_Wj?-9$L4EE(r$s{kM*UF*vZS?NdC#i4fg>eS;~XttbWeJkHA%t=7r`| zAMT2t8%s!V@+NWJ+N>_Ar$fS+vO58qiBTI6Io#yu&=W^Ea9@jebxw>P~7;vZ9QtXWW`mYbjm`QS{9%1+P+^@rg;<*Vt4^raXr( zI{LONNiE78oy7a_DsZkwFcf}t?iMM-rzCuqQIATtzFIB;{b`MWdKUaC6gzZG4|-M@ z=P-_W&)2OqIVa_h1Ju*JwF4&^rWRB=+(k5mKV+n5I27C;;*gW*1x9ZNKpC-5Swk*% zk@cWts!lMV{xvWz2k`ziSIs?!f2BA`PgUvdQp!Zd&Q4oC!-|4i-M=cYG>pag1Fx+S z+NO8EB=*i}1IV#4KTfT#m(^EPtT_;L$B(q#gC;c^-Abu5T^@uJl z$hJbGvu;p7opE=%RfXK-ytmIG9TmnuT1y`e>E3UZb#l=rINLZG{KhLuMJ-&3i|C{H zn%8#b<;a&?f)qWKmp@LuYB>BYp(`0KFWytT@0pcH^Q1oxGzVcQyq9lW?+u!rejn)y z!#1}r2S#~!1NaJ_e$xB}%dWAP-dMNObQ^h!+AB*kbSUOfKU&7ugc1f4>DS2W4jMHe zAH_%WuFp%gvC`xFJ*J)_$5NzV{{ULpTQ~q|7!$bis=5ADMs6C6Kx3f3cpH>!;DEVSS5PdtE^!wOtVhHhDLnEFxxc>m_RSic#(De&H zDobA`M;Xjf{JHw#oa)AE#KN56speiOzL69|7P6Nyp&^1X+%d;~4RW$VvJ`hNsz)1- zB-ekdcs|Qc21`bldJN@)=kTs_-84w=8~a8}#XXA<0Y2Ct*0!e@sOV=oPEOycjrJ9d z6qU&CLZ9VIwvp{(p?80@auk0pM3TF>#LPKGpFo~#iyub zB^gyVk~?y7nweLOSSk~HBgoRtR}5j1@_9(fVh^|(6{T;X>fkJiZ8|E9u45&!^u=|T ze-U)6dv;3*7CBXZMG1^IsmB$|-TXlD73qH|))?4r+Nw$8`BPJrwm|8|KNOY8Ej&A_ zNprm{Cw4u2z(5~~s3y|&$CJ!hW&mYaw%&cPYO-qjHX28fF3v5Z zl*qq$J8lQ@Ju5<9!bt2BGfb@}a#cDX*NSXc2){gb=+B;aNJ7$xV2ox>hTwn8HEurx z-nA_>9a8x>TItd-;||Cd@;&HnEx_8*{BNgOpY)wAp+-;%goywnxZoewtxNGD#6nSR zB=b2Vat)ia$9#?}mAld}^timslz4-jDx5GU-1e;XHxV2>QQ-70wS%D#eM0P0_rXyiUyJ35f%&cm-w3%4gPf_=V}^orUhV(ep6js#$z#OAecv_G=n`6A*Qk`%E>(5V1@as2Bm%|2$x z!*XyGNAmt86HQ4yP&l=^u<;x(mW~-DQT$DX6(5jX?oELQ1-Frp!xZM0XBhL7XzQ?? z3XO{b9^1~(MU?gT#a+m->7#*mhn_iG_=iG25GYp*2g+oLn4Ct0@;!O)QHH?WZP}&I z5^n@A)C!PYF~B7d{{VQ$&S`2=(TN&dPRkq%cROI@HsV0kw= z6(U$$xMFT%0ebwycjrjtJ2#(*@zc%7U&soH(8gbE*^Rnv7+=WLmXMDfyhjnKWgQ7{ zN&KqShNT^p>RQ6q?Fam`kRCrOryEBTw&qzJbS$Hf(yTQ5QG+_EJd&hf{vBzwnzI%4 zIz30n7c%LQ+SuAjXvhv_S%Q^5fMXb}dAvcZUTHAeO$2OX$TDx`,|D$Q4o{!tEJ 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 z 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%0DS;?(D2>o&FJ8`Fu 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 literal 0 HcmV?d00001 diff --git a/clearcode-toolkit/docs/ClearCode Introduction-June 2020.pdf b/clearcode-toolkit/docs/ClearCode Introduction-June 2020.pdf new file mode 100644 index 0000000000000000000000000000000000000000..25009c256d91c42052bfdd9554fb2cf8e8964a7f GIT binary patch literal 586265 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 z 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ⅈW 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 --ask-become-pass site.yml + + +Common operations +----------------- + + +Local testing in a VM +~~~~~~~~~~~~~~~~~~~~~ +For local testing in a VM, first install a bare VM with Ubuntu Serevr 16.X LTS. +You only need the OpenSSH server and need to be able to SSH in the VM. +This is typically achieved by using Bridge Networking in VirtualBox. + +- SSH in the VM to get its IP address with `$ ifconfig`. +- Given an IP of ww.xx.yy.zz, update the `hosts-test` sample host file with this local IP, + e.g. the IP of your VM in a [testvm] role + Do NOT use the `hosts` inventory file in you ansible commands. +- Then you can run ansible with this test config using your VM user password that will be + asked twice. You may need to have `sshpass` installed. Or you can setup SSH key auth + otherwise for password-less SSH'ing in the VM.:: + + ansible-playbook -i hosts-test ---verbose --ask-become-pass site.yml + +When testing you may want to locally change the vars.yml playbook to fetch +alternative test branches. diff --git a/clearcode-toolkit/etc/ansible/hosts b/clearcode-toolkit/etc/ansible/hosts new file mode 100644 index 00000000..eed88973 --- /dev/null +++ b/clearcode-toolkit/etc/ansible/hosts @@ -0,0 +1,2 @@ +[server] +127.0.0.1:2222 diff --git a/clearcode-toolkit/etc/ansible/roles/common/tasks/main.yml b/clearcode-toolkit/etc/ansible/roles/common/tasks/main.yml new file mode 100644 index 00000000..14ae208e --- /dev/null +++ b/clearcode-toolkit/etc/ansible/roles/common/tasks/main.yml @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- name: Update and upgrade installed packages + apt: + update_cache: yes + upgrade: full + cache_valid_time: 600 + +- name: Install or update packages + apt: + name: "{{ packages }}" + state: latest + cache_valid_time: 600 + update_cache: yes diff --git a/clearcode-toolkit/etc/ansible/roles/postgres/tasks/main.yml b/clearcode-toolkit/etc/ansible/roles/postgres/tasks/main.yml new file mode 100644 index 00000000..8ba7918c --- /dev/null +++ b/clearcode-toolkit/etc/ansible/roles/postgres/tasks/main.yml @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- name: Install PostgreSQL packages + apt: + pkg: + - postgresql + - postgresql-server-dev-10 + - python-psycopg2 # required by ansible for the database setup + - python3.6-psycopg2 # required by ansible for the database setup + +- name: Ensure PostgreSQL service is started and enabled at boot + service: + name: postgresql + state: started + enabled: yes + +- name: Create database user + become: yes + become_user: postgres + postgresql_user: + name: "{{ project_name }}" + password: "cl34-u5er" # FIXME: Put this in a vault + role_attr_flags: NOCREATEROLE,NOSUPERUSER,CREATEDB,LOGIN,INHERIT + state: present + +- name: Create database + become: yes + become_user: postgres + postgresql_db: + name: "{{ project_name }}" + owner: "{{ project_name }}" + encoding: UTF-8 + template: template0 + lc_collate: en_US.UTF-8 + lc_ctype: en_US.UTF-8 + state: present + +- name: Restart PostgreSQL service + become: yes + become_user: root + service: + name: postgresql + state: restarted + + # TODO: impliment backup scheme (probably do not want this to be everyday, due to db size + #- name: Create project backups directory + # file: + # path: "/var/backups/{{ project_name }}" + # state: directory + # owner: postgres + # group: postgres + # + #- name: Setup daily database backup in the postgres user crontab + # cron: + # name: "Daily database backups at 01:00am" + # user: postgres + # minute: "0" + # hour: "1" + # job: 'pg_dump -Fc clearcode > /var/backups/clearcode/clearcode_`date "+\%Y-\%m-\%d_\%H\%M"`.dump' + # diff --git a/clearcode-toolkit/etc/ansible/roles/project/tasks/main.yml b/clearcode-toolkit/etc/ansible/roles/project/tasks/main.yml new file mode 100644 index 00000000..ff4fa3e3 --- /dev/null +++ b/clearcode-toolkit/etc/ansible/roles/project/tasks/main.yml @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- name: Create project group + group: + name: "{{ project_name }}" + state: present + +- name: Create project user + user: + name: "{{ project_name }}" + group: "{{ project_name }}" + state: present + create_home: no + shell: /bin/bash + +# Copy the public key `ssh_public_key` value from the ansible output +# into the github deploy keys at https://github.com/nexB/scancode.io/settings/keys +# Title: scancodeio@.scancode.io +- name: Create a 4096-bit RSA SSH key for project user in .ssh/id_rsa + user: + name: "{{ project_name }}" + generate_ssh_key: yes + ssh_key_type: rsa + ssh_key_bits: 4096 + ssh_key_file: .ssh/id_rsa + +- name: Create project /opt directory + file: + path: "/opt/{{ project_name }}" + state: directory + owner: "{{ project_name }}" + group: "{{ project_name }}" + +- name: Create project /var directory + file: + path: "/var/{{ project_name }}" + state: directory + owner: "{{ project_name }}" + group: "{{ project_name }}" + +- name: Create project /etc directory + file: + path: "/etc/{{ project_name }}" + state: directory + owner: "{{ project_name }}" + group: "{{ project_name }}" + +- name: Fetch project git repository + become: true + become_user: "{{ project_name }}" + become_method: sudo + git: + repo: "{{ project_repo }}" + dest: "/opt/{{ project_name }}" + version: "{{ git_branch }}" + accept_hostkey: true + +- name: Ensure everything is owned by project user + owner + become: true + become_user: root + become_method: sudo + file: + path: "/opt/{{ project_name }}" + state: directory + owner: "{{ project_name }}" + group: "{{ project_name }}" + recurse: true + +- name: Configure the project + become: true + become_user: "{{ project_name }}" + become_method: sudo + command: ./configure + args: + chdir: "/opt/{{ project_name }}/" diff --git a/clearcode-toolkit/etc/ansible/site.yml b/clearcode-toolkit/etc/ansible/site.yml new file mode 100644 index 00000000..c65ee076 --- /dev/null +++ b/clearcode-toolkit/etc/ansible/site.yml @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- name: Deploy clearcode server + hosts: all + become: yes + become_user: root + become_method: sudo + vars_files: + - vars.yml + + roles: + - common + - postgres + - project diff --git a/clearcode-toolkit/etc/ansible/vars.yml b/clearcode-toolkit/etc/ansible/vars.yml new file mode 100644 index 00000000..754bed62 --- /dev/null +++ b/clearcode-toolkit/etc/ansible/vars.yml @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +hostname: clearcode +project_name: clearcode +project_home: /opt/clearcode +project_repo: ssh://git@github.com/nexB/clearcode-toolkit.git + +git_branch: master + +packages: + - git + - python3-dev + - python3-venv + - postgresql + - postgresql-server-dev-10 + - gcc + - screen + - fail2ban diff --git a/clearcode-toolkit/etc/scripts/clearcode-api-backup.py b/clearcode-toolkit/etc/scripts/clearcode-api-backup.py new file mode 100644 index 00000000..d11c4d34 --- /dev/null +++ b/clearcode-toolkit/etc/scripts/clearcode-api-backup.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Script example to backup clearcode data using the clearcode API. + +Pre-requisite: + - A local installation of Python + - The Python "requests" library, installed with "pip install requests". + + Run the backup script with: + python clearcode-api-backup.py YYYY-MM-DD + + A directory "clearcode_backup_" will be created in the same directory + that contains this script, and running this script will create one JSON backup + file. +""" + +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + +import argparse +import json +import os +import logging +import sys +from collections import defaultdict +from datetime import datetime +from os.path import abspath, dirname, exists, join + +try: + from urllib import urlencode +except ImportError: + from urllib.parse import urlencode + +try: + import requests +except ImportError: + print('The "requests" library is required by this script.\n' + 'Install it with: "pip install requests"') + sys.exit(1) + +logging.captureWarnings(True) + + +class ProgressBar(object): + progress_width = 75 + + def __init__(self, output, total_count): + self.output = output + self.total_count = total_count + self.prev_done = 0 + + def update(self, count): + if not self.output: + return + perc = count * 100 // self.total_count + done = perc * self.progress_width // 100 + if self.prev_done >= done: + return + self.prev_done = done + cr = '' if self.total_count == 1 else '\r' + self.output.write(cr + '[' + '.' * done + ' ' * (self.progress_width - done) + ']') + if done == self.progress_width: + self.output.write('\n') + self.output.flush() + + +def get_all_objects_from_endpoint(url, extra_payload=None, verbose=True): + """ + Return a list of all objects by calling the clearcode API endpoint `url` + with the provided request. Paginate as needed. + """ + objects = [] + payload = {} + if extra_payload: + payload.update(extra_payload) + + output = sys.stdout + count_done = 0 + progress_bar = None + + next_url = '{}?{}'.format(url, urlencode(payload)) + while next_url: + response = requests.get(next_url) + if response.status_code == requests.codes.ok: + response_json = response.json() + if verbose and not progress_bar: + total_count = response_json.get('count') + if not total_count: + return [] + print('{} total'.format(total_count)) + progress_bar = ProgressBar(output, total_count) + results = response_json.get('results') + objects.extend(results) + if verbose: + count_done += len(results) + progress_bar.update(count_done) + next_url = response_json.get('next') + else: + print('Error. Please restart the script.') + sys.exit(1) + + return objects + + +def run_api_backup(api_root_url, extra_payload=None): + """ + Execute a backup of clearcode data objects to JSON files. + Given: + - an `api_root_url` clearcode API root URL and + this function: + - creates a new backup directory named "clearcode_backup_" + side-by-side with this script + - calls the clearcode API to collect the list of all objects for each + API endpoint + - writes JSON files named after each endpoint with these collected + objects in the backup directory + On errors, this function will exit Python with a return code of 1. + """ + endpoints = [ + 'cditems', + ] + + # Ensure all those dependencies are available in the backup file to feed the copy script. + # Not needed when --last_modified_date is not provided since all the objects + # for each endpoint will be collected. + results = defaultdict(list) + + for endpoint_name in endpoints: + endpoint_url = '{}{}/'.format(api_root_url, endpoint_name) + + print('Collecting {}...'.format(endpoint_name)) + objects = get_all_objects_from_endpoint(endpoint_url, extra_payload=extra_payload) + print('{} {} collected.'.format(len(objects), endpoint_name)) + + collect_extra_conditions = [ + extra_payload.get('last_modified_date'), + ] + + results[endpoint_name] += objects + + timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + backup_dir = join(abspath(dirname(__file__)), 'clearcode_backup_{}'.format(timestamp)) + if not exists(backup_dir): + os.mkdir(backup_dir) + + for endpoint_name, objects in results.items(): + backup_file = join(backup_dir, '{}.json'.format(endpoint_name)) + assert not exists(backup_file) + with open(backup_file, 'w') as f: + f.write(json.dumps(objects, indent=2)) + + print('Backup location: {}'.format(backup_dir)) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='clearcode data backup using the clearcode API', + ) + parser.add_argument( + '--api-root-url', + help='clearcode API endpoints root URL.', + default='http://127.0.0.1:8000/api/', + ) + parser.add_argument( + '--last-modified-date', + help='Limit the backup to object created/modified after that date. Format: "YYYY-MM-DD"', + required=True, + ) + args = parser.parse_args() + + extra_payload = {} + try: + datetime.strptime(args.last_modified_date, '%Y-%m-%d') + except ValueError: + print('Incorrect last_modified_date format. Expected YYYY-MM-DD') + sys.exit(1) + extra_payload['last_modified_date'] = args.last_modified_date + + print('Starting backup from {}'.format(args.api_root_url)) + run_api_backup(args.api_root_url, extra_payload) + print('Backup completed.') + sys.exit(0) diff --git a/clearcode-toolkit/etc/scripts/clearcode-api-import.py b/clearcode-toolkit/etc/scripts/clearcode-api-import.py new file mode 100644 index 00000000..878507e8 --- /dev/null +++ b/clearcode-toolkit/etc/scripts/clearcode-api-import.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Script to import clearcode data using the clearcode API. + +Pre-requisite: + - A local installation of Python + - The Python "requests" library, installed with "pip install requests". + - a clearcode backup directory, (output of running clearcode-api-backup.py) + +After completion, the clearcode database will be updated with the items from the +clearcode backup +""" + +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + +import argparse +import json +import os +import logging +import sys +from datetime import datetime +from os.path import abspath, dirname, join +from collections import defaultdict + +try: + import requests +except ImportError: + print('The "requests" library is required by this script.\n' + 'Install it with: "pip install requests"') + sys.exit(1) + +logging.captureWarnings(True) + + +def run_api_copy(api_root_url, backup_directory): + headers = { + 'Content-type': 'application/json', + } + + endpoints = [ + 'cditems', + ] + + copy_results = {} + for endpoint in endpoints: + backup_file = os.path.join(backup_directory, '{}.json'.format(endpoint)) + + if not os.path.exists(backup_file): + print('{} backup file is not available, skipped.'.format(endpoint.title())) + continue + + with open(backup_file) as f: + source_objects = json.load(f) + + api_endpoint_url = '{}{}/'.format(api_root_url, endpoint) + if requests.get(api_endpoint_url, headers=headers).status_code != 200: + print('{} API endpoint not available.'.format(endpoint.title())) + continue + + print('Copying {} {}...'.format(len(source_objects), endpoint)) + endpoint_results = defaultdict(list) + for i, data in enumerate(source_objects): + if not (i % 10): + print('.', end='', flush=True) + object_api_url = '{}{}/'.format(api_endpoint_url, data['uuid']) + response = requests.get(object_api_url, headers=headers) + object_exists = response.status_code == 200 + + if object_exists: + put_response = requests.put(object_api_url, headers=headers, data=json.dumps(data)) + if put_response.status_code == 200: # Updated + endpoint_results['updated'].append(data) + else: + print('Update error:', put_response and put_response.json() or repr(put_response.content)) + endpoint_results['update_errors'].append({'data': data, 'error': put_response.json()}) + + else: + post_response = requests.post(api_endpoint_url, headers=headers, data=json.dumps(data)) + if post_response.status_code == 201: # Created + endpoint_results['created'].append(data) + else: + print('Create error:', post_response.json()) + endpoint_results['create_errors'].append({'data': data, 'error': post_response.json()}) + copy_results[endpoint] = endpoint_results + return copy_results + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='clearcode data import using the clearcode API', + ) + parser.add_argument( + '--clearcode-target-api-url', + help='clearcode target instance API endpoints root URL. http://hostname/api/', + default='http://127.0.0.1:8000/api/', + ) + parser.add_argument( + '--backup-directory', + help='Path of the backup directory created by clearcode-api-backup.py script', + required=True, + ) + args = parser.parse_args() + + if not args.clearcode_target_api_url: + print('A clearcode target instance API endpoints root URL is required.\n' + 'Provide one using the --clearcode-target-api-url argument.') + sys.exit(1) + + backup_directory = args.backup_directory + + if not backup_directory.startswith('/'): + cwd = os.getcwd() + backup_directory = abspath(join(cwd, backup_directory)) + + if not os.path.exists(backup_directory): + print('Directory "{}" does not exists.'.format(backup_directory)) + sys.exit(1) + + print('Importing objects from {} to {}'.format(backup_directory, args.clearcode_target_api_url)) + copy_results = run_api_copy(args.clearcode_target_api_url, backup_directory) + timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + output_file = join(abspath(dirname(__file__)), 'copy_results_{}.json'.format(timestamp)) + with open(output_file, 'w') as f: + f.write(json.dumps(copy_results, indent=2)) + print('Copy completed.') + print('Results saved in {}'.format(output_file)) + sys.exit(0) diff --git a/clearcode-toolkit/manage.py b/clearcode-toolkit/manage.py new file mode 100755 index 00000000..48912909 --- /dev/null +++ b/clearcode-toolkit/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "clearcode.dbsettings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/clearcode-toolkit/requirements.txt b/clearcode-toolkit/requirements.txt new file mode 100644 index 00000000..7b775876 --- /dev/null +++ b/clearcode-toolkit/requirements.txt @@ -0,0 +1,19 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# pip-compile +# +asgiref==3.2.7 # via django +attrs==19.3.0 # via clearcode-toolkit (setup.py) +certifi==2020.4.5.1 # via requests +chardet==3.0.4 # via requests +click==7.1.1 # via clearcode-toolkit (setup.py) +django==3.0.7 # via clearcode-toolkit (setup.py), djangorestframework +djangorestframework==3.11.0 # via clearcode-toolkit (setup.py) +idna==2.9 # via requests +psycopg2==2.8.5 # via clearcode-toolkit (setup.py) +pytz==2019.3 # via django +requests==2.23.0 # via clearcode-toolkit (setup.py) +sqlparse==0.3.1 # via django +urllib3==1.25.9 # via requests diff --git a/clearcode-toolkit/setup.cfg b/clearcode-toolkit/setup.cfg new file mode 100644 index 00000000..d4bbcac4 --- /dev/null +++ b/clearcode-toolkit/setup.cfg @@ -0,0 +1,50 @@ +[metadata] +license_files = + NOTICE + apache-2.0.LICENSE + README.rst + + +[aliases] +release = clean --all sdist bdist_wheel + + +[tool:pytest] +norecursedirs = + .git + bin + dist + build + _build + dist + etc + local + ci + docs + man + share + samples + .cache + .settings + Include + include + Lib + lib + lib64 + Lib64 + Scripts + thirdparty + tmp + src/*/data + tests/*/data + +python_files = *.py + +python_classes=Test +python_functions=test + +addopts = + -rfExXw + --strict + --ignore setup.py + --doctest-modules diff --git a/clearcode-toolkit/setup.py b/clearcode-toolkit/setup.py new file mode 100644 index 00000000..874f90fe --- /dev/null +++ b/clearcode-toolkit/setup.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from __future__ import absolute_import +from __future__ import print_function + +import io +from glob import glob +from os.path import basename +from os.path import dirname +from os.path import join +from os.path import splitext +import re + +from setuptools import find_packages +from setuptools import setup + + +setup( + name='clearcode-toolkit', + version='0.0.3', + license='Apache-2.0', + description='ClearCode is a tool to sync ClearlyDefined data.', + long_description='ClearCode is a tool to sync ClearlyDefined scans and curations.', + author='nexB Inc. and others', + author_email='info@aboutcode.org', + url='https://github.com/nexB/clearcode-toolkit', + packages=find_packages('src'), + package_dir={'': 'src'}, + py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')], + include_package_data=True, + zip_safe=False, + classifiers=[ + # complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.7', + 'Topic :: Utilities', + ], + keywords=[ + 'open source', + 'scan', + 'license', + 'package', + 'clearlydefined', + 'scancode', + ], + install_requires=[ + 'attrs', + 'click', + 'django', + 'psycopg2', + 'requests', + 'djangorestframework', + 'packageurl-python', + ], + entry_points={ + 'console_scripts': [ + 'clearsync = clearcode.sync:cli', + 'clearload = clearcode.load:cli', + ], + }, +) diff --git a/clearcode-toolkit/src/clearcode/__init__.py b/clearcode-toolkit/src/clearcode/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/clearcode-toolkit/src/clearcode/api.py b/clearcode-toolkit/src/clearcode/api.py new file mode 100644 index 00000000..42bcd6c1 --- /dev/null +++ b/clearcode-toolkit/src/clearcode/api.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import base64 + +from django.urls import include, re_path +from rest_framework import routers +from rest_framework import serializers +from rest_framework import viewsets + +from clearcode.models import CDitem + + +class CDitemContentFieldSerializer(serializers.Field): + """ + Custom Field Serializer used to translate between Django ORM binary field and + base64-encoded string + """ + def to_representation(self, obj): + return base64.b64encode(obj).decode('utf-8') + + def to_internal_value(self, data): + return base64.b64decode(data) + + +class CDitemSerializer(serializers.HyperlinkedModelSerializer): + """ + Custom Serializer used to serialize the CDitem model + """ + content = CDitemContentFieldSerializer(required=False) + class Meta: + model = CDitem + fields = ( + 'path', + 'uuid', + 'content', + 'last_modified_date', + 'last_map_date', + 'map_error', + ) + + +class CDitemViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows CDitems to be viewed. + """ + serializer_class = CDitemSerializer + lookup_field = 'uuid' + + def get_queryset(self): + last_modified_date = self.request.query_params.get('last_modified_date', None) + queryset = CDitem.objects.all() + + if last_modified_date: + queryset = CDitem.objects.modified_after(last_modified_date) + + return queryset + + +router = routers.DefaultRouter() +router.register(r'cditems', CDitemViewSet, 'cditems') + +urlpatterns = [ + re_path('^api/', include((router.urls, 'api'))), +] diff --git a/clearcode-toolkit/src/clearcode/cdutils.py b/clearcode-toolkit/src/clearcode/cdutils.py new file mode 100644 index 00000000..477aac87 --- /dev/null +++ b/clearcode-toolkit/src/clearcode/cdutils.py @@ -0,0 +1,561 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +from hashlib import md5 +from itertools import zip_longest +import os +from os import path +import subprocess +import time +from urllib.parse import urlsplit +from urllib.parse import urlunsplit +from urllib.parse import parse_qs +from urllib.parse import quote_plus +from urllib.parse import unquote_plus + +import attr +import click +from packageurl import PackageURL +import requests + + +""" +ClearlyDefined utlities. +""" + +TRACE_FETCH = False +TRACE = False +TRACE_DEEP = False + + +PACKAGE_TYPES_BY_CD_TYPE = { + 'crate': 'cargo', + 'deb': 'deb', + 'debsrc': 'deb', + # Currently used only for maven packages + 'sourcearchive': 'maven', + 'maven': 'maven', + 'composer': 'composer', + # Currently used only for Github repo/packages + 'git': 'github', + 'pod': 'pod', + 'nuget': 'nuget', + 'pypi': 'pypi', + 'gem': 'gem', +} + + +PACKAGE_TYPES_BY_PURL_TYPE = { + 'cargo': 'crate', + 'deb': 'deb', + 'maven': 'maven', + 'composer': 'composer', + 'github': 'git', + 'pod': 'pod', + 'nuget': 'nuget', + 'pypi': 'pypi', + 'gem': 'gem', + 'npm': 'npm', +} + + +PROVIDERS_BY_PURL_TYPE = { + 'cargo': 'cratesio', + 'deb': 'debian', + 'maven': 'mavencentral', + 'composer': 'packagist', + # Currently used only for Github repo/packages + 'git': 'github', + 'github': 'github', + 'pod': 'cocoapods', + 'nuget': 'nuget', + 'pypi': 'pypi', + 'gem': 'rubygem', + 'npm': 'npmjs', +} + + +QUALIFIERS_BY_CD_TYPE = { + 'sourcearchive': {'classifier': 'sources'}, + 'debsrc': {'arch': 'source'} +} + + +@attr.s(slots=True) +class Coordinate(object): + """ + ClearlyDefined coordinates are used to identify any tracked component. + """ + + base_api_url = 'https://api.clearlydefined.io' + + type = attr.ib() + provider = attr.ib() + namespace = attr.ib() + name = attr.ib() + revision = attr.ib() + + def __attrs_post_init__(self, *args, **kwargs): + if self.provider == 'debian': + self.namespace = 'debian' + if not self.namespace: + self.namespace = '-' + + @classmethod + def from_dict(cls, coords): + if 'namespace' not in coords: + coords['namespace'] = '-' + return cls(**coords) + + def to_dict(self): + return attr.asdict(self) + + @classmethod + def from_path(cls, pth, root=None): + """ + Return a Coordinate from a path and an optional root. + + If a root is provided, the root is stripped from the path prefix. + + The remaining path is assumed to have its 5 leading segments mapping to + the coordinate elements. + + For instance: + + >>> expected = Coordinate('maven', 'mavencentral', 'io.dropwizard', 'dropwizard', '2.0.0-rc13') + >>> p = '/maven/mavencentral/io.dropwizard/dropwizard/2.0.0-rc13/' + >>> test = Coordinate.from_path(p) + >>> assert expected == test + + >>> p = '/maven/mavencentral/io.dropwizard/dropwizard/2.0.0-rc13/scancode/3.2.2/' + >>> test = Coordinate.from_path(p) + >>> assert expected == test + + >>> u = 'https://api.clearlydefined.io/harvest/maven/mavencentral/io.dropwizard/dropwizard/2.0.0-rc13' + >>> root = 'https://api.clearlydefined.io/harvest' + >>> test = Coordinate.from_path(u, root) + >>> assert expected == test + + >>> u = 'https://api.clearlydefined.io/harvest/maven/mavencentral/io.dropwizard/dropwizard/2.0.0-rc13/scancode/3.2.2' + >>> root = 'https://api.clearlydefined.io/harvest' + >>> test = Coordinate.from_path(u, root) + >>> assert expected == test + + >>> p = '/maven/mavencentral/io.dropwizard/dropwizard/revision/2.0.0-rc13/tool/scancode/3.2.2.json' + >>> test = Coordinate.from_path(p) + >>> assert expected == test + + >>> p = '/maven/mavencentral/io.dropwizard/dropwizard/revision/2.0.0-rc13.json' + >>> test = Coordinate.from_path(p) + >>> assert expected == test + + """ + pth = pth.strip('/') + if root and root in pth: + root = root.strip('/') + _, _, pth = pth.partition(root) + + segments = pth.strip('/').split('/') + if len(segments) >= 6 and segments[4] == 'revision': + # AZ blob style + # /maven/mavencentral/io.dropwizard/dropwizard/revision/2.0.0-rc13.json + # /maven/mavencentral/io.dropwizard/dropwizard/revision/2.0.0-rc13/tool/scancode/3.2.2.json + start = segments[:4] + version = segments[5] + if version.endswith('.json'): + version, _, _ = version.rpartition('.json') + segments = start + [version] + else: + # plain API paths do not have a /revision/ segment + segments = segments[:5] + return cls(*segments) + + def to_api_path(self): + return '{type}/{provider}/{namespace}/{name}/{revision}'.format(**self.to_dict()) + + def to_def_blob_path(self): + return '{type}/{provider}/{namespace}/{name}/revision/{revision}.json'.format(**self.to_dict()) + + def to_harvest_blob_path(self, tool, tool_version): + return '{type}/{provider}/{namespace}/{name}/revision/{revision}/tool/{tool}/{tool_version}.json'.format( + tool=tool, tool_version=tool_version, + **self.to_dict()) + + def get_definition_api_url(self, base_api_url=None): + """ + Return a URL to fetch the full definition. + """ + return '{base_url}/definitions/{type}/{provider}/{namespace}/{name}/{revision}'.format( + base_url=base_api_url or self.base_api_url, + path=self.to_api_path(), + **self.to_dict()) + + def get_harvests_api_url(self, base_api_url=None): + """ + Return a URL to fetch all harvests at once. + """ + return '{base_url}/harvest/{type}/{provider}/{namespace}/{name}/{revision}?form=raw'.format( + base_url=base_api_url or self.base_api_url, + path=self.to_api_path(), + **self.to_dict()) + + def to_def_query_api_url(self, include_revision=False, base_api_url=None): + """ + Return a CD API URL for query definitions. + """ + qs = 'type={type}&provider={provider}&name{name}' + if include_revision: + qs += '&revision={revision}' + if self.namespace and self.namespace != '-': + qs += '&namespace={namespace}' + qs = qs.format( + base_url=base_api_url or self.base_api_url, + **self.to_dict()) + return '{base_url}/definitions?{qs}'.format(**locals()) + + def to_purl(self): + """ + Return a PackageURL string containing this Coordinate's information + + >>> expected = 'pkg:maven/io.dropwizard/dropwizard@2.0.0-rc13' + >>> test = Coordinate('maven', 'mavencentral', 'io.dropwizard', 'dropwizard', '2.0.0-rc13').to_purl() + >>> assert expected == test + + >>> expected = 'pkg:maven/io.dropwizard/dropwizard@2.0.0-rc13?classifier=sources' + >>> test = Coordinate('sourcearchive', 'mavencentral', 'io.dropwizard', 'dropwizard', '2.0.0-rc13').to_purl() + >>> assert expected == test + + >>> expected = 'pkg:deb/debian/gedit-plugins@3.34.0-3?arch=source' + >>> test = Coordinate('debsrc', 'debian', '', 'gedit-plugins', '3.34.0-3').to_purl() + >>> assert expected == test + """ + converted_package_type = PACKAGE_TYPES_BY_CD_TYPE[self.type] + + namespace = '' + if self.namespace != '-': + namespace = self.namespace + + if self.provider == 'debian': + namespace = 'debian' + + qualifiers = {} + if self.type in ('debsrc', 'sourcearchive',): + qualifiers = QUALIFIERS_BY_CD_TYPE[self.type] + + return PackageURL( + type=converted_package_type, + namespace=namespace, + name=self.name, + version=self.revision, + qualifiers=qualifiers, + ).to_string() + + @classmethod + def from_purl(cls, purl): + """ + Return a Coordinate containing the information from PackageURL `purl` + + >>> expected = Coordinate('maven', 'mavencentral', 'io.dropwizard', 'dropwizard', '2.0.0-rc13') + >>> purl = 'pkg:maven/io.dropwizard/dropwizard@2.0.0-rc13' + >>> test = Coordinate.from_purl(purl) + >>> assert expected == test + + >>> expected = Coordinate('sourcearchive', 'mavencentral', 'io.dropwizard', 'dropwizard', '2.0.0-rc13') + >>> purl = 'pkg:maven/io.dropwizard/dropwizard@2.0.0-rc13?classifier=sources' + >>> test = Coordinate.from_purl(purl) + >>> assert expected == test + + >>> expected = Coordinate('debsrc', 'debian', '', 'gedit-plugins', '3.34.0-3') + >>> purl = 'pkg:deb/debian/gedit-plugins@3.34.0-3?arch=source' + >>> test = Coordinate.from_purl(purl) + >>> assert expected == test + """ + p = PackageURL.from_string(purl) + + package_type = p.type + if package_type not in PACKAGE_TYPES_BY_PURL_TYPE: + raise Exception('Package type is not supported by ClearlyDefined: {}'.format(package_type)) + # Handle the source types of Maven and Debian packages + if package_type == 'maven' and p.qualifiers.get('classifier', '') == 'sources': + package_type = 'sourcearchive' + provider = 'mavencentral' + elif package_type == 'deb' and p.qualifiers.get('arch', '') == 'source': + package_type = 'debsrc' + provider = 'debian' + else: + package_type = PACKAGE_TYPES_BY_PURL_TYPE[package_type] + # TODO: Have way to set other providers? + provider = PROVIDERS_BY_PURL_TYPE[package_type] + + return cls( + type=package_type, + provider=provider, + namespace=p.namespace, + name=p.name, + revision=p.version, + ) + + +def get_coordinates(data_dir): + """ + Yield tuple of (path, Coordinate) from definition directories from `data_dir` + at full depth. + """ + data_dir = data_dir.strip('/') + for dirpath, dirnames, _filenames in os.walk(data_dir, followlinks=False): + for d in dirnames: + pth = path.join(dirpath, d) + _, _, cdpth = pth.partition(data_dir) + segments = cdpth.strip('/').split('/') + # skip paths that have not the full depth required (e.g. 5 segments) + if not len(segments) == 5: + continue + yield pth, Coordinate.from_path(cdpth) + + +def _get_response_content(url, retries=2, wait=2, session=requests, verbose=False, _retries=set()): + """ + Return a tuple of (etag, md5, content bytes) with the content as bytes or as decoded + text if `as_text` is True) of the response of a GET HTTP request at `url`. + On HTTP errors (500 or higher), retry up to `retries` time after waiting + `wait` seconds. + """ + if verbose: + click.echo(' --> Fetching: {url}'.format(**locals())) + + response = session.get(url, timeout=600) + status_code = response.status_code + + if status_code == requests.codes.ok: # NOQA + # handle the case where the API returns an empty file and we need + # to restart from an earlier continuation + if url in _retries: + _retries.remove(url) + print(' SUCCESS after Failure to fetch:', url) + etag = response.headers.get('etag') + content = response.content + checksum = md5(content).hexdigest() + return etag, checksum, response.content + + error_code = requests.codes.get(status_code) or '' + + if status_code >= 500 and retries: + # timeout/522 or other server error: let's wait a bit and retry for "retries" number of retries + retries -= 1 + print(' Failure to fetch:', url, 'with', status_code, error_code, 'retrying after waiting:', wait, 'seconds.') + _retries.add(url) + time.sleep(wait) + return _get_response_content( + url=url, retries=retries, wait=wait, session=session, verbose=verbose) + + # all other errors + raise Exception('Failed HTTP request for {url} : error: {status_code} : {error_code}'.format(**locals())) + + +def get_response_content(url, retries=2, wait=4, session=requests, verbose=False): + """ + Return the bytes of the response of a GET HTTP request at `url`, an md5 checksum and the URL etag. + On failures, retry up to `retries` time after waiting `wait` seconds. + """ + try: + return _get_response_content( + url=url, retries=retries, wait=wait, + session=session, verbose=verbose) + except Exception as e: + if retries: + print(' Failure to fetch:', url, 'with error:', e, 'and retrying after waiting:', wait, 'seconds.') + # we sleep progressively more after each failure and up to wait seconds + time.sleep(int(wait / (retries or 1))) + retries -= 1 + return get_response_content( + url=url, retries=retries, wait=wait, + session=session, verbose=verbose) + else: + raise + + +def split_url(url): + """ + Given a URL, return a tuple of URL elements where `query` is a mapping. + """ + scheme, netloc, path, query, fragment = urlsplit(url) + query = parse_qs(query) + return scheme, netloc, path, query, fragment + + +def join_qs(keys_values, do_not_quote=()): + """ + Join a key/values mapping back into a query string. + Quote values unless the name is in in the `do_not_quote` set. + """ + keys_values = { + k: (v[0] if v and isinstance(v, list) else v) for k, v in keys_values.items()} + return '&'.join('='.join([k, v if k in do_not_quote else quote_plus(v)]) + for k, v in keys_values.items()) + + +def append_path_to_url(url, extra_path): + """ + Return a new `url` with `extra_path` appended to its path. + """ + scheme, netloc, path, query, fragment = split_url(url) + path = path.strip('/') + '/' + extra_path.strip('/') + segments = scheme, netloc, path, join_qs(query), fragment + return urlunsplit(segments) + + +def update_url(url, qs_mapping, do_not_quote=()): + """ + Return a new `url` with its query string updated from a mapping of key/value pairs. + """ + scheme, netloc, path, query, fragment = split_url(url) + query.update(qs_mapping) + segments = scheme, netloc, path, join_qs(query, do_not_quote=do_not_quote), fragment + return urlunsplit(segments) + + +def build_cdapi_continuation_url(api_url, continuation_token): + """ + Return a new `api_url` with a CD API `continuation_token`. + """ + return update_url(api_url, {'continuationToken': continuation_token}) + + +def build_cdapi_continuation_url_from_coordinates(api_url, coordinates): + """ + Return a new `api_url` with a continuation token built from + a `coordinates` string. If a token is already present in the api_url it + will be replaced. + """ + continuation_token = get_cdapi_continuation_token(coordinates) + return build_cdapi_continuation_url(api_url, continuation_token) + + +def split_cdapi_url(url): + """ + Given a URL that may contain a continuation token, return a tuple of + (cleaned url, token) + """ + # get a continuation-free base URL. This assumes that the continuationToken + # is always the last query string param if it is present. + scheme, netloc, url, query, fragment = split_url(url) + token = query.pop('continuationToken', None) + if token: + token = token[0] + if '%' in token: + token = unquote_plus(token) + segments = scheme, netloc, url, join_qs(query), fragment + unparsed = urlunsplit(segments) + if TRACE: + print('split_cdapi_url:', 'unparsed:', unparsed, 'token:', token) + return unparsed, token + + +def get_coord_from_cdapi_continuation_url(api_url): + """ + Given a URL that may contain a continuation token, return that as a decoded + CD coordinate string or None. + """ + # get a continuation-free base URL. This assumes that the continuationToken + # is always the last query string param if it is present. + _url, token = split_cdapi_url(api_url) + if token: + return get_coord_from_cdapi_continuation(token) + + +def get_coord_from_cdapi_continuation(continuation): + """ + Given an encoded continuation token, return a string of CD coordinates. + """ + if TRACE: + print('get_coord_from_cdapi_continuation: continuation:', continuation) + continuation = continuation.replace(' ', '+') + + if '%' in continuation: + continuation = unquote_plus(continuation) + + decoded = base64.b64decode(continuation) + if not isinstance(decoded, str): + decoded = decoded.decode('utf-8') + return decoded + + +def get_cdapi_continuation_token(coord): + """ + Given a coord mapping or string of CD coordinates, return an encoded + continuation token. + """ + if isinstance(coord, dict): + coord = coord2str(coord) + coord = coord.replace(' ', '+') + encoded = coord.encode('utf-8') + + return base64.b64encode(encoded).decode('utf-8') + + +def str2coord(s): + """ + Return a mapping of CD coordinates from a `s` coordinates, URL or URN string. + + Some example of the supported input strings are: + URL: "cd:/gem/rubygems/-/mocha/1.7.0" + URN: "urn:gem:rubygems:-:mocha:revision:1.7.0:tool:scancode:3.1.0" + plain: /gem/rubygems/foo/mocha/1.7.0" + """ + is_urn = s.startswith('urn') + is_url = s.startswith('cd:') + splitter = ':' if is_urn else '/' + segments = s.strip(splitter).split(splitter) + if is_urn or is_url: + segments = segments[1:] + # ignore extra segments for now beyond the 5 fisrt (such as the PR of a curation) + segments = segments[:5] + + fields = ('type', 'provider', 'namespace', 'name', 'revision',) + return dict(zip_longest(fields, segments)) + + +def coord2str(coord): + """ + Return a path-like from a `coord` CD coordinates mapping. + A non-present namespace is always represented as a dash (-) + + A mapping as these fields: + "type": "git", + "provider": "github", + "namespace": "nexb", + "name": "license-expression", + "revision": "70277cdfc186466667cb58ec9f9c7281e68a221b" + """ + assert coord, 'Empty or missing coordinate mapping: {}'.format(coord) + rev = coord.get('revision') + kwargs = dict( + t=coord['type'], + p=coord['provider'], + ns=coord.get('namespace') or '-', + n=coord['name'], + r=rev, + ) + if rev: + template = '{t}/{p}/{ns}/{n}/{r}' + else: + template = '{t}/{p}/{ns}/{n}' + return template.format(**kwargs) + diff --git a/clearcode-toolkit/src/clearcode/dbconf.py b/clearcode-toolkit/src/clearcode/dbconf.py new file mode 100644 index 00000000..d7e3565c --- /dev/null +++ b/clearcode-toolkit/src/clearcode/dbconf.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import django +from django.apps import apps +from django.conf import settings + +from clearcode import dbsettings + + +""" +Configuration helpers for the embedded sqlite DB used through Django's ORM. +You must call configure() before calling or importing anything else from ScanCode. + +WARNING: DO NOT USE THESE IF YOU WANT TO USE SCANCODE IN A REGULAR DJANGO WEB APP. +THE SETTINGS DEFINED HERE ARE ONLY FOR USING SCANCODE AS A COMMAND LINE OR WHEN USING +SCANCODE AS A LIBRARY OUTSIDE OF A DJANGO WEB APPLICATION. +""" + + +def configure(settings_module=dbsettings, verbose=False): + """ + Configure minimally Django (in particular the ORM and DB) using the + `settings_modules` module. When using as a library you must call this + function at least once before calling other code. + """ + if not settings.configured: + settings.configure(**get_settings(settings_module)) + # call django.setup() only once + if not apps.ready: + django.setup() + create_db(verbose=verbose) + + +def get_settings(settings_module=dbsettings): + """ + Return a mapping of UPPERCASE settings from a module + """ + # settings are all UPPERCASE + sets = [s for s in dir(dbsettings) if s.isupper()] + return {s: getattr(dbsettings, s) for s in sets} + + +def create_db(verbose=False, _already_created=[]): + """ + Create the database by invoking the migrate command behind the scenes. + """ + if not _already_created: + verbosity = 2 if verbose else 0 + from django.core.management import call_command + call_command('migrate', verbosity=verbosity, interactive=False, run_syncdb=True) + _already_created.append(True) diff --git a/clearcode-toolkit/src/clearcode/dbsettings.py b/clearcode-toolkit/src/clearcode/dbsettings.py new file mode 100644 index 00000000..6ed40c46 --- /dev/null +++ b/clearcode-toolkit/src/clearcode/dbsettings.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Django Settings for ClearCode DB. +""" + +DATABASES = dict( + default=dict( + ENGINE='django.db.backends.postgresql', + HOST='localhost', + PORT='5432', + NAME='packagedb', + USER='packagedb', + PASSWORD='packagedb', + ATOMIC_REQUESTS=True, + ) +) + +# TODO: Add some sort of api auth if you expose to the world +ALLOWED_HOSTS = ['localhost', '127.0.0.1'] + +ROOT_URLCONF = 'clearcode.api' + +# To disable running migration on each run, enable a fake migration below +# class FakeMigrations(dict): +# """ +# Fake migration class to avoid running migrations at all. For the CLI +# usage, a new DB is created on each run, so running migrations has no value. +# inspired by https://github.com/henriquebastos/django-test-without-migrations +# """ +# def __getitem__(self, item): +# return 'do not run any migrations' +# +# def __contains__(self, item): +# return True +# MIGRATION_MODULES = dbconf.FakeMigrations() + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'clearcode', + 'rest_framework', +] + +MIDDLEWARE = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +) + +REST_FRAMEWORK = { + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', + 'PAGE_SIZE': 100, + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.AllowAny', + ], + 'DEFAULT_RENDERER_CLASSES': [ + 'rest_framework.renderers.JSONRenderer', + ], +} + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + } + } +] + +# SECURITY WARNING: keep the secret key used in a production webapp! +# here we are running a local CLI-only application and do not need a Django secret +SECRET_KEY = '# SECURITY WARNING: keep the secret key used in a production webapp!' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = False + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# On Unix systems, a value of None will cause Django to use the same +# timezone as the operating system. +TIME_ZONE = 'UTC' + +STATIC_URL = '/static/' + diff --git a/clearcode-toolkit/src/clearcode/load.py b/clearcode-toolkit/src/clearcode/load.py new file mode 100644 index 00000000..56de2b4a --- /dev/null +++ b/clearcode-toolkit/src/clearcode/load.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import multiprocessing +import os +from pathlib import Path +import sys + +from django.db.utils import IntegrityError + +import click + + +""" +Load ClearlyDefined definitions and harvests from the filesystem + +Operation +--------- +This script walks a given `--input-dir` location and loads any ClearlyDefined data +into a Database (currently postgreSQL). + +Usage +----- +$ clearload --input-dir ~/path/to/ClearlyDefined/dir +""" + + +def walk_and_load_from_filesystem(input_dir, cd_root_dir): + """ + Walk the given input_dir and load clearlydefined data into a Database. + A CD item on the filesystem looks like the following: + + ~/clearly-local/npm/npmjs/@actions/github/revision/2.1.1.json.gz + + The resulting CDitem should be: + + CDitem.path = npm/npmjs/@actions/github/revision/2.1.1.json.gz + CDitem.content = 'the file: 2.1.1.json.gz in bytes' + """ + from clearcode import dbconf + dbconf.configure() + + # for now, we count dirs too + file_counter = 1 + for root, dirs, files in os.walk(input_dir): + for filename in files: + # output some progress + print(' ', end='\r') + print("Processing file #{}".format(file_counter), end='\r') + file_counter +=1 + + # TODO: check if the location is actually a CD data item. + full_gzip_path = os.path.join(root, filename) + full_json_path = full_gzip_path.rstrip('.gz') + + # normalize the `path` value by removing the arbitrary parent directory + cditem_rel_path = os.path.relpath(full_json_path, cd_root_dir) + + with open(full_gzip_path, mode='rb') as f: + content = f.read() + + from clearcode import models + # Save to DB + try: + cditem = models.CDitem.objects.create(path=cditem_rel_path, content=content) + except IntegrityError: + # skip if we already have it in the DB + continue + + +@click.command() + +@click.option('--input-dir', + type=click.Path(), metavar='DIR', + help='Load content from this input directory that contains a tree of gzip-compressed JSON CD files') + +@click.option('--cd-root-dir', + type=click.Path(), metavar='DIR', + help='specify root directory that contains a tree of gzip-compressed JSON CD files') + +@click.help_option('-h', '--help') + +def cli(input_dir=None, cd_root_dir=None, *arg, **kwargs): + """ + Handle ClearlyDefined gzipped JSON scans by walking a clearsync directory structure, + creating CDItem objects and loading them into a PostgreSQL database. + """ + if not input_dir: + sys.exit('Please specify an input directory using the `--input-dir` option.') + if not cd_root_dir: + sys.exit('Please specify the cd-root-directory using the --cd-root-dir option.') + + # get proper DB setup + + walk_and_load_from_filesystem(input_dir, cd_root_dir) + print(' ', end='\r') + print("Loading complete") + + +if __name__ == '__main__': + cli() diff --git a/clearcode-toolkit/src/clearcode/migrations/0001_initial.py b/clearcode-toolkit/src/clearcode/migrations/0001_initial.py new file mode 100644 index 00000000..4fb7f4a5 --- /dev/null +++ b/clearcode-toolkit/src/clearcode/migrations/0001_initial.py @@ -0,0 +1,22 @@ +# Generated by Django 3.0.4 on 2020-03-06 18:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='CDitem', + fields=[ + ('path', models.CharField(help_text='Path to the original file in the ClearlyDefined file storage.', max_length=2048, primary_key=True, serialize=False)), + ('content', models.BinaryField(help_text='Actual gzipped JSON content.')), + ('last_modified_date', models.DateTimeField(auto_now=True, help_text='Date and time that this record was last modified.')), + ], + ), + ] diff --git a/clearcode-toolkit/src/clearcode/migrations/0002_auto_20200331_1052.py b/clearcode-toolkit/src/clearcode/migrations/0002_auto_20200331_1052.py new file mode 100644 index 00000000..67411c62 --- /dev/null +++ b/clearcode-toolkit/src/clearcode/migrations/0002_auto_20200331_1052.py @@ -0,0 +1,23 @@ +# Generated by Django 3.0.4 on 2020-03-31 10:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('clearcode', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='cditem', + name='last_map_date', + field=models.DateTimeField(blank=True, db_index=True, help_text='Timestamp set to the date of the last mapping. Used to track mapping status.', null=True), + ), + migrations.AddField( + model_name='cditem', + name='map_error', + field=models.TextField(blank=True, help_text='Mapping errors messages. When present this means the mapping failed.', null=True), + ), + ] diff --git a/clearcode-toolkit/src/clearcode/migrations/0003_cditem_uuid.py b/clearcode-toolkit/src/clearcode/migrations/0003_cditem_uuid.py new file mode 100644 index 00000000..3da8737e --- /dev/null +++ b/clearcode-toolkit/src/clearcode/migrations/0003_cditem_uuid.py @@ -0,0 +1,34 @@ +# Generated by Django 3.0.5 on 2020-05-04 18:33 + +from django.db import migrations, models +import uuid + + +def create_uuid(apps, schema_editor): + CDitem = apps.get_model('clearcode', 'CDitem') + # Use `iterator()` to prevent queryset from cacheing. + # See: https://docs.djangoproject.com/en/3.0/ref/models/querysets/#iterator + for item in CDitem.objects.iterator(): + item.uuid = uuid.uuid4() + item.save(update_fields=['uuid']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('clearcode', '0002_auto_20200331_1052'), + ] + + operations = [ + migrations.AddField( + model_name='cditem', + name='uuid', + field=models.UUIDField(blank=True, null=True) + ), + migrations.RunPython(create_uuid), + migrations.AlterField( + model_name='cditem', + name='uuid', + field=models.UUIDField(default=uuid.uuid4, unique=True, editable=False) + ) + ] diff --git a/clearcode-toolkit/src/clearcode/migrations/__init__.py b/clearcode-toolkit/src/clearcode/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/clearcode-toolkit/src/clearcode/models.py b/clearcode-toolkit/src/clearcode/models.py new file mode 100644 index 00000000..0b96f2ee --- /dev/null +++ b/clearcode-toolkit/src/clearcode/models.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import gzip +import json +import uuid + +from django.db import models + + +class VirtualFileStore: + """ + Convenience wrapper to access CDitems as if they would be concrete files. + """ + @classmethod + def walk(self, prefix=None, since=None): + """ + Yield tuples of (path, data, last modified) for CD items. + Optionally return items that have a certain path prefix. + Optionally return items that have have been modified `since` a datetime. + """ + items = CDitem.objects.all() + if prefix: + items = items.filter(path__startswith=prefix) + if since: + items = items.filter(last_modified_date__ge=since) + for item in items: + yield item.path, item.data_content(), item.last_modified_date + + +class CDitemQuerySet(models.QuerySet): + def known_package_types(self): + # These are the Package types that can be stored in the PackageDB + KNOWN_PACKAGE_TYPES = [ + 'composer', + 'crate', + 'deb', + 'debsrc', + 'gem', + 'git', + 'maven', + 'npm', + 'nuget', + 'pypi', + 'sourcearchive', + ] + q_objs = models.Q() + for package_type in KNOWN_PACKAGE_TYPES: + q_objs.add(models.Q(path__startswith=package_type), models.Q.OR) + return self.filter(q_objs) + + def definitions(self): + return self.exclude(path__contains='/tool/') + + def scancode_harvests(self): + return self.filter(path__contains='tool/scancode') + + def mappable(self): + return self.filter(last_map_date__isnull=True, map_error__isnull=True) + + def mappable_definitions(self): + return self.mappable().definitions().known_package_types() + + def mappable_scancode_harvests(self): + return self.mappable().scancode_harvests().known_package_types() + + def modified_after(self, date): + """ + Limit the QuerySet to CDitems that were modified after a given `date`. + """ + return self.filter(last_modified_date__gt=date) + + +class CDitem(models.Model): + """ + A simple key/value pair model where the key is the path to a JSON file as + stored in ClearlyDefined blob storage and the value is a GZipped compressed + JSON file content, stored as a binary bytes blob. + """ + path = models.CharField(primary_key=True, max_length=2048, + help_text='Path to the original file in the ClearlyDefined file storage.' + ) + + uuid = models.UUIDField( + default=uuid.uuid4, + unique=True, + editable=False, + ) + + content = models.BinaryField( + help_text='Actual gzipped JSON content.' + ) + + last_modified_date = models.DateTimeField( + help_text='Date and time that this record was last modified.', + auto_now=True, # Automatically set to now on object save() + ) + + last_map_date = models.DateTimeField( + null=True, + blank=True, + db_index=True, + help_text='Timestamp set to the date of the last mapping. ' + 'Used to track mapping status.', + ) + + map_error = models.TextField( + null=True, + blank=True, + help_text='Mapping errors messages. When present this means the mapping failed.', + ) + + objects = CDitemQuerySet.as_manager() + + @property + def data(self): + """ + Return the data content deserialized from the content field. + """ + uncompressed_content = gzip.decompress(self.content) + if not uncompressed_content: + uncompressed_content = '{}' + return json.loads(uncompressed_content) diff --git a/clearcode-toolkit/src/clearcode/sync.py b/clearcode-toolkit/src/clearcode/sync.py new file mode 100644 index 00000000..b744da86 --- /dev/null +++ b/clearcode-toolkit/src/clearcode/sync.py @@ -0,0 +1,584 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from datetime import datetime +import gzip +import json +from multiprocessing import pool +import os +from os import path +import time + +import click +from django.utils import timezone +import requests + +from clearcode import cdutils + + +""" +Fetch the latest definitions and harvests from ClearlyDefined + +Theory of operation +------------------- + +We can access on a regular basis a limited subset of the most recently updated +definitions, by batches of 100 definitions using this query: + https://api.clearlydefined.io/definitions?matchCasing=false&sort=releaseDate&sortDesc=true + +We can also focus this on a type as in: + https://api.clearlydefined.io/definitions?matchCasing=false&sort=releaseDate&sortDesc=true&type=git + +This provides incomplete definitions (they do not contain files). + +From there we can fetch: + - each definition (or possibly many as a batch at once) + - each harvest either by batching all harvest tools at once or fetching them + one by one. + - any referenced attachment + +Since the definition batches are roughly stopping after 2000 when sorted by +latest date, we can repeat this every few minutes or so forever to catch any +update. We use etags and a cache to avoid refetching things that have not +changed. +""" + +TRACE = False + + +# TODO: update when this is updated upstream +# https://github.com/clearlydefined/service/blob/master/schemas/definition-1.0.json#L17 +known_types = ( + # fake empty type + None, + 'npm', + 'git', + 'pypi', + 'composer', + 'maven', + 'gem', + 'nuget', + 'sourcearchive', + 'deb', + 'debsrc', + 'crate', + 'pod', +) + + +# each process gets its own session +session = requests.Session() + + +def fetch_and_save_latest_definitions( + base_api_url, cache, output_dir=None, save_to_db=False, + by_latest=True, retries=2, verbose=True): + """ + Fetch ClearlyDefined definitions and paginate through. Save these as blobs + to data_dir. + + Fetch the most recently updated definitions first if `by_latest` is True. + Otherwise, the order is not specified. + NOTE: these do not contain file details (but the harvest do) + """ + assert output_dir or save_to_db, 'You must select one of the --output-dir or --save-to-db options.' + + if save_to_db: + from clearcode import dbconf + dbconf.configure(verbose=verbose) + + definitions_url = cdutils.append_path_to_url(base_api_url, extra_path='definitions') + if by_latest: + definitions_url = cdutils.update_url(definitions_url, qs_mapping=dict(sort='releaseDate', sortDesc='true')) + + for content in fetch_definitions(api_url=definitions_url, cache=cache, retries=retries, verbose=TRACE): + # content is a batch of 100 definitions + definitions = content and content.get('data') + if not definitions: + if verbose: + print(' No more data for: {}'.format(definitions_url)) + break + + if verbose: + first = cdutils.coord2str(definitions[0]['coordinates']) + last = cdutils.coord2str(definitions[-1]['coordinates']) + print('Fetched definitions from :', first, 'to:', last, flush=True) + else: + print('.', end='', flush=True) + + savers = [] + if save_to_db: + savers.append(db_saver) + if output_dir: + savers.append(file_saver) + + # we received a batch of definitions: let's save each as a Gzipped JSON + for definition in definitions: + coordinate = cdutils.Coordinate.from_dict(definition['coordinates']) + for saver in savers: + blob_path, _size = save_def( + coordinate=coordinate, content=definition, output_dir=output_dir, + saver=saver) + yield coordinate, blob_path + + +def fetch_definitions(api_url, cache, retries=1, verbose=True): + """ + Yield batches of definitions each as a list of mappings from calling the + ClearlyDefined API at `api_url`. Retry on failure up to `retries` times. + Raise an exception on failure. Raise an EmptyReponse on success but empty + response (a CD API quirk). + + Paginate using the API's `continuationToken`. If provided as a start, this + token should be in the initial URL query string. + + The structure of the REST payload is a list : + {"data": [{}, ...], "continuationToken": ""} + """ + assert '/definitions' in api_url + content = None + errors_count = 0 + max_errors = 5 + while True: + try: + content = cache.get_content(api_url, retries=retries, session=session) + if not content: + break + content = json.loads(content) + + except requests.exceptions.ConnectionError as ex: + print('!!!!!!!!!!!!!!!!!! -> Request failed, retrying:', api_url, 'with:', ex) + errors_count += 1 + if errors_count <= max_errors: + # wait and retry, sleeping more each time we egt some error + time.sleep(errors_count * 3) + continue + else: + raise + + continuation_token = '' + if content: + yield content + continuation_token = content.get('continuationToken', '') + + if not continuation_token: + if verbose: + print(' No more data for: {}'.format(api_url)) + break + + api_url = cdutils.build_cdapi_continuation_url(api_url, continuation_token) + + +def compress(content): + """ + Return a byte string of `content` gzipped-compressed. + `content` is eiher a string or a JSON-serializable data structure. + """ + if isinstance(content, str): + content = content.encode('utf-8') + else: + content = json.dumps(content , separators=(',', ':')).encode('utf-8') + return gzip.compress(content, compresslevel=9) + + +def file_saver(content, blob_path, output_dir, **kwargs): + """ + Save `content` bytes (or dict or string) as gzip compressed bytes to `file_path`. + Return the length of the written payload or 0 if it existed and was not updated. + """ + file_path = path.join(output_dir, blob_path + '.gz') + compressed = compress(content) + + if path.exists(file_path): + with open(file_path , 'rb') as ef: + existing = ef.read() + if existing == compressed: + return 0 + else: + parent_dir = path.dirname(file_path) + os.makedirs(parent_dir, exist_ok=True) + + with open(file_path , 'wb') as oi: + if TRACE: + print('Saving:', blob_path) + oi.write(compressed) + return len(compressed) + + +def db_saver(content, blob_path, **kwargs): + """ + Save `content` bytes (or dict or string) identified by `file_path` to the + configured DB. Return the length of the written payload or 0 if it existed + and was not update. + """ + from clearcode import models + + compressed = compress(content) + + cditem, created = models.CDitem.objects.get_or_create(path=blob_path) + if not created: + if cditem.content != compressed and cditem.last_modified_date < timezone.now(): + cditem.content = compressed + cditem.save() + if TRACE: + print('Updating content for:', blob_path) + else: + return 0 + else: + if TRACE: + print('Adding content for:', blob_path) + + return len(compressed) + + +def save_def(coordinate, content, output_dir, saver=file_saver): + """ + Save the definition `content` bytes (or dict or string) for `coordinate` + object to `output_dir` using blob paths conventions. + + Return a tuple of the ( saved file path, length of the written payload). + """ + blob_path = coordinate.to_def_blob_path() + return blob_path, saver(content=content, output_dir=output_dir, blob_path=blob_path) + + +def save_harvest( + coordinate, tool, tool_version, content, output_dir, saver=file_saver): + """ + Save the scan `content` bytes (or dict or string) for `tool` `tool_version` + of `coordinate` object to `output_dir` using blob paths conventions. + + Return a tuple of the ( saved file path, length of the written payload). + """ + blob_path = coordinate.to_harvest_blob_path(tool, tool_version) + return blob_path, saver(content=content, output_dir=output_dir, blob_path=blob_path) + + +def fetch_and_save_harvests( + coordinate, cache, output_dir=None, save_to_db=False, retries=2, + session=session, verbose=True): + """ + Fetch all the harvests for `coordinate` Coordinate object and save them in + `outputdir` using blob-style paths, one file for each harvest/scan. + + (Note: Return a tuple of (etag, md5, url) for usage as a callback) + """ + assert output_dir or save_to_db, 'You must select one of the --output-dir or --save-to-db options.' + if save_to_db: + from clearcode import dbconf + dbconf.configure(verbose=verbose) + + url = coordinate.get_harvests_api_url() + etag, checksum, content = cache.get_content( + url, retries=retries, session=session, with_cache_keys=True) + + if content: + savers = [] + if save_to_db: + savers.append(db_saver) + if output_dir: + savers.append(file_saver) + + if verbose: + print(' Fetched harvest for:', coordinate.to_api_path(), flush=True) + else: + print('.', end='', flush=True) + + for tool, versions in json.loads(content).items(): + for tool_version, harvest in versions.items(): + for saver in savers: + save_harvest( + coordinate=coordinate, + tool=tool, + tool_version=tool_version, + content=harvest, + output_dir=output_dir, + saver=saver) + + return etag, checksum, url + + +class Cache(object): + """ + A caching object for etags and checksums to avoid refetching things. + """ + + def __init__(self, max_size=100 * 1000): + self.etags_cache = {} + self.checksums_cache = {} + self.max_size = max_size + + def is_unchanged_remotely(self, url, session=session): + """ + Return True if a `url` content is unchanged from cache based on HTTP + HEADER Etag. + """ + try: + response = session.head(url) + remote_etag = response.headers.get('etag') + if remote_etag and self.etags_cache.get(url) == remote_etag: + return True + except: + return False + + def is_fetched(self, checksum, url): + """ + Return True if the content checksum exists for url, using MD5 checksum. + """ + return url and checksum and self.checksums_cache.get(checksum) == url + + def add(self, etag, checksum, url): + if etag: + self.etags_cache[url] = etag + if checksum: + self.checksums_cache[checksum] = url + + def add_args(self, args): + self.add(*args) + + def trim(self): + """ + Trim the cache to its max size. + """ + + def _resize(cache): + extra_items = len(cache) - self.max_size + if extra_items > 0: + for ei in list(cache)[:extra_items]: + del cache[ei] + + _resize(self.etags_cache) + _resize(self.checksums_cache) + + def get_content(self, url, retries=1, session=session, with_cache_keys=False): + """ + Return fetched content as bytes or None if already fetched or unchanged. + Updates the cache as needed. + """ + if self.is_unchanged_remotely(url=url, session=session): + return + + etag, checksum, content = cdutils.get_response_content( + url, retries=retries, session=session) + + if not content: + return + + if self.is_fetched(checksum, url): + return + + self.add(etag, checksum, url) + + if with_cache_keys: + return etag, checksum, content + else: + return content + + def copy(self): + """ + Return a deep copy of self + """ + cache = Cache(self.max_size) + cache.checksums_cache = dict(self.checksums_cache) + cache.etags_cache = dict(self.etags_cache) + return cache + + +@click.command() + +@click.option('--output-dir', + type=click.Path(), metavar='DIR', + help='Save fetched content as compressed gzipped files to this output directory.') + +@click.option('--save-to-db', + is_flag=True, + help='Save fetched content as compressed gzipped blobs in the configured database.') + +@click.option('--unsorted', + is_flag=True, + help='Fetch data without any sorting. The default is to fetch data sorting by latest updated first.') + +@click.option('--base-api-url', + type=str, + default='https://api.clearlydefined.io', show_default=True, + help='ClearlyDefined base API URL.') + +@click.option('--wait', + type=int, metavar='INT', + default=60, show_default=True, + help='Set the number of seconds to wait for new or updated definitions ' + 'between two loops.') + +@click.option('-n', '--processes', + type=int, metavar='INT', + default=1, show_default=True, + help='Set the number of parallel processes to use. ' + 'Disable parallel processing if 0.') + +@click.option('--max-def', + type=int, metavar='INT', + default=0, + help='Set the maximum number of definitions to fetch.') + +@click.option('--only-definitions', + is_flag=True, + help='Only fetch definitions and no other data item.') + +@click.option('--log-file', + type=click.Path(), default=None, + help='Path to a file where to log fetched paths, one per line. ' + 'Log entries will be appended to this file if it exists.') + +@click.option('--verbose', + is_flag=True, + help='Display more verbose progress messages.') + +@click.help_option('-h', '--help') +def cli(output_dir=None, save_to_db=False, + base_api_url='https://api.clearlydefined.io', + wait=60, processes=1, unsorted=False, + log_file=None, max_def=0, only_definitions=False, session=session, + verbose=False, *arg, **kwargs): + """ + Fetch the latest definitions and harvests from ClearlyDefined and save these + as gzipped JSON either as as files in output-dir or in a PostgreSQL + database. Loop forever after waiting some seconds between each cycles. + """ + assert output_dir or save_to_db, 'You must select at least one of the --output-dir or --save-to-db options.' + + fetch_harvests = not only_definitions + + cycles = 0 + total_defs_count = 0 + total_duration = 0 + + coordinate = None + file_path = None + + cache = Cache(max_size=100 * 1000) + + sleeping = False + harvest_fetchers = None + + log_file_fn = None + if log_file: + log_file_fn = open(log_file, 'a') + + try: + if fetch_harvests: + harvest_fetchers = pool.Pool(processes=processes, maxtasksperchild=10000) + + # loop forever. Complete one loop once we have fetched all the latest + # items and we are not getting new pages (based on etag) + # Sleep between each loop + while True: + start = time.time() + cycles += 1 + cycle_defs_count = 0 + + # iterate all types to get more depth for the latest defs. + for def_type in known_types: + sleeping = False + + if def_type: + # get latest with a "type" query + def_api_url = cdutils.update_url(base_api_url, qs_mapping=dict(type=def_type)) + else: + # do nothing if we have no type + def_api_url = base_api_url + + definitions = fetch_and_save_latest_definitions( + base_api_url=def_api_url, + output_dir=output_dir, + save_to_db=save_to_db, + cache=cache, + by_latest=not unsorted, + verbose=verbose) + + for coordinate, file_path in definitions: + + cycle_defs_count += 1 + + if log_file: + log_file_fn.write(file_path.partition('.gz')[0] + '\n') + + if TRACE: print(' Saved def for:', coordinate) + + if fetch_harvests: + kwds = dict( + coordinate=coordinate, + output_dir=output_dir, + save_to_db=save_to_db, + # that's a copy of the cache, since we are in some + # subprocess, the data is best not shared to avoid + # any sync issue + cache=cache.copy(), + verbose=verbose) + + harvest_fetchers.apply_async( + fetch_and_save_harvests, + kwds=kwds, + callback=cache.add_args) + + if max_def and max_def <= cycle_defs_count: + break + + if max_def and (max_def <= cycle_defs_count or max_def <= total_defs_count): + break + + total_defs_count += cycle_defs_count + cycle_duration = time.time() - start + total_duration += cycle_duration + + if not sleeping: + print('Saved', cycle_defs_count, 'defs and harvests,', + 'in:', int(cycle_duration), 'sec.') + + print('TOTAL cycles:', cycles, + 'with:', total_defs_count, 'defs and combined harvests,', + 'in:', int(total_duration), 'sec.') + + print('Cycle completed at:', datetime.utcnow().isoformat(), + 'Sleeping for', wait, 'seconds...') + else: + print('.', end='') + + sleeping = True + time.sleep(wait) + cache.trim() + + except KeyboardInterrupt: + click.secho('\nAborted with Ctrl+C!', fg='red', err=True) + return + + finally: + if log_file: + log_file_fn.close() + + if harvest_fetchers: + harvest_fetchers.close() + harvest_fetchers.terminate() + + print('TOTAL cycles:', cycles, + 'with:', total_defs_count, 'defs and combined harvests,', + 'in:', int(total_duration), 'sec.') + + +if __name__ == '__main__': + cli() diff --git a/clearcode-toolkit/src/clearcode/tests/__init__.py b/clearcode-toolkit/src/clearcode/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/clearcode-toolkit/src/clearcode/tests/test_api.py b/clearcode-toolkit/src/clearcode/tests/test_api.py new file mode 100644 index 00000000..76f3feba --- /dev/null +++ b/clearcode-toolkit/src/clearcode/tests/test_api.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import base64 +import datetime +import gzip +import json + +from django.test import TestCase +from django.utils import timezone +from rest_framework import status +from rest_framework.test import APIClient + +from clearcode import api +from clearcode.models import CDitem + + +class CDitemSerializerTestCase(TestCase): + + def setUp(self): + self.cditem_attributes = { + 'path': 'test/path/file.json', + 'content': gzip.compress(json.dumps({'test': 'content'}).encode('utf-8')) + } + self.cditem = CDitem.objects.create(**self.cditem_attributes) + self.serializer = api.CDitemSerializer(instance=self.cditem) + self.data = self.serializer.data + + def test_contains_expected_fields(self): + self.assertCountEqual(self.data.keys(), ['path', 'uuid', 'content', 'last_modified_date', 'last_map_date', 'map_error']) + + def test_path_field_content(self): + self.assertEqual(self.data['path'], self.cditem_attributes['path']) + + def test_content_field_content(self): + decoded_test_data = base64.b64decode(self.data['content']) + self.assertEqual(decoded_test_data, self.cditem_attributes['content']) + self.assertEqual(json.loads(gzip.decompress(decoded_test_data)), {'test': 'content'}) + + def test_last_map_date_field_content(self): + self.assertIsNone(self.data['last_map_date']) + + def test_map_error_field_content(self): + self.assertIsNone(self.data['map_error']) + + +class CDitemAPITestCase(TestCase): + + def setUp(self): + self.client = APIClient() + self.test_path = 'test/path/file.json' + + self.post_test_path = 'test/post/path/file.json' + + self.test_data = {'test': 'content'} + self.test_content = gzip.compress(json.dumps(self.test_data).encode('utf-8')) + + self.cditem = CDitem.objects.create(path=self.test_path) + self.uuid = self.cditem.uuid + + def test_api_cditems_get(self): + response = self.client.get('/api/cditems/{}/'.format(self.uuid)) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data.get('path'), self.test_path) + self.assertEqual(response.data.get('uuid'), str(self.uuid)) + + def test_api_cditems_get_list(self): + response = self.client.get('/api/cditems/') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(1, response.data.get('count')) + + def test_api_cditems_get_list_by_last_modified_date_old_date(self): + test_date = datetime.datetime.now() - datetime.timedelta(days=1) + test_date_string = '{}-{}-{}'.format(test_date.year, test_date.month, test_date.day) + + response = self.client.get('/api/cditems/?last_modified_date={}'.format(test_date_string)) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(1, response.data.get('count')) + + def test_api_cditems_get_list_by_last_modified_date_future(self): + test_date = datetime.datetime.now() + datetime.timedelta(days=1) + test_date_string = '{}-{}-{}'.format(test_date.year, test_date.month, test_date.day) + + response = self.client.get('/api/cditems/?last_modified_date={}'.format(test_date_string)) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(0, response.data.get('count')) + + def test_api_cditems_put(self): + test_payload = { + 'path': self.test_path, + 'content': base64.b64encode(self.test_content).decode('utf-8') + } + + response = self.client.put('/api/cditems/{}/'.format(self.uuid), test_payload) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + cditem = CDitem.objects.get(path=self.test_path) + self.assertEqual(cditem.data, self.test_data) + + def test_api_cditems_post(self): + test_payload = { + 'path': self.post_test_path, + 'content': base64.b64encode(self.test_content).decode('utf-8') + } + + response = self.client.post('/api/cditems/', test_payload) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data.get('path'), self.post_test_path) + + cditem = CDitem.objects.get(path=self.post_test_path) + self.assertEqual(cditem.data, self.test_data) diff --git a/clearcode-toolkit/src/clearcode/tests/test_models.py b/clearcode-toolkit/src/clearcode/tests/test_models.py new file mode 100644 index 00000000..541ac8bf --- /dev/null +++ b/clearcode-toolkit/src/clearcode/tests/test_models.py @@ -0,0 +1,104 @@ +# +# Copyright (c) 2020 by nexB, Inc. http://www.nexb.com/ - All rights reserved. +# +import datetime + +from django.test import TestCase +from django.utils import timezone + +from clearcode.models import CDitem + + +class CDitemManagerModifiedAfterTestCase(TestCase): + + def setUp(self): + self.cditem0 = CDitem.objects.create(path='npm/name/version') + + def test_modified_after_1_day_old(self): + test_date = datetime.datetime.now() - datetime.timedelta(days=1) + self.assertIsNotNone(CDitem.objects.modified_after(test_date)) + self.assertEqual(1, len(CDitem.objects.modified_after(test_date))) + + def test_modified_after_1_week_old(self): + test_date = datetime.datetime.now() - datetime.timedelta(days=7) + self.assertIsNotNone(CDitem.objects.modified_after(test_date)) + self.assertEqual(1, len(CDitem.objects.modified_after(test_date))) + + def test_modified_after_1_day_new(self): + test_date = datetime.datetime.now() + datetime.timedelta(days=1) + self.assertIsNotNone(CDitem.objects.modified_after(test_date)) + self.assertEqual(0, len(CDitem.objects.modified_after(test_date))) + + def test_modified_after_1_week_new(self): + test_date = datetime.datetime.now() + datetime.timedelta(days=7) + self.assertIsNotNone(CDitem.objects.modified_after(test_date)) + self.assertEqual(0, len(CDitem.objects.modified_after(test_date))) + + +class CDitemManagerTestCase(TestCase): + def test_known_package_types(self): + # This path starts with npm, which is known + cditem_1 = CDitem.objects.create(path='npm/name/version') + # asdf is not a proper type + cditem_2 = CDitem.objects.create(path='asdf/name/version') + cditems = list(CDitem.objects.known_package_types()) + self.assertEqual(1, len(cditems)) + cditem = cditems[0] + self.assertEqual(cditem_1, cditem) + + def test_definitions(self): + expected_definition = CDitem.objects.create(path='composer/packagist/yoast/wordpress-seo/revision/9.5-RC3.json') + # harvest should not be in cditems + harvest = CDitem.objects.create(path='sourcearchive/mavencentral/io.nats/jnats/revision/2.6.6/tool/scancode/3.2.2.json') + cditems = list(CDitem.objects.definitions()) + self.assertEqual(1, len(cditems)) + definition = cditems[0] + self.assertEqual(expected_definition, definition) + + def test_scancode_harvests(self): + expected_harvest = CDitem.objects.create(path='sourcearchive/mavencentral/io.nats/jnats/revision/2.6.6/tool/scancode/3.2.2.json') + # unexpected_harvest should not be in cditems + unexpected_harvest = CDitem.objects.create(path='sourcearchive/mavencentral/io.nats/jnats/revision/2.6.6/tool/licensee/9.13.0.json') + harvests = list(CDitem.objects.scancode_harvests()) + self.assertEqual(1, len(harvests)) + harvest = harvests[0] + self.assertEqual(expected_harvest, harvest) + + def test_mappable(self): + definition_1 = CDitem.objects.create(path='sourcearchive/mavencentral/io.nats/jnats/revision/2.6.6.json') + definition_2 = CDitem.objects.create( + path='sourcearchive/mavencentral/io.quarkus/quarkus-jsonb/revision/0.26.1.json', + last_map_date=timezone.now(), + map_error='error' + ) + harvest = CDitem.objects.create(path='sourcearchive/mavencentral/io.nats/jnats/revision/2.6.6/tool/scancode/3.2.2.json') + mappables = list(CDitem.objects.mappable()) + self.assertEqual(2, len(mappables)) + self.assertIn(definition_1, mappables) + self.assertIn(harvest, mappables) + + def test_mappable_definitions(self): + definition_1 = CDitem.objects.create(path='sourcearchive/mavencentral/io.nats/jnats/revision/2.6.6.json') + definition_2 = CDitem.objects.create( + path='sourcearchive/mavencentral/io.quarkus/quarkus-jsonb/revision/0.26.1.json', + last_map_date=timezone.now(), + map_error='error' + ) + harvest = CDitem.objects.create(path='sourcearchive/mavencentral/io.nats/jnats/revision/2.6.6/tool/scancode/3.2.2.json') + mappables = list(CDitem.objects.mappable_definitions()) + self.assertEqual(1, len(mappables)) + definition = mappables[0] + self.assertEqual(definition_1, definition) + + def test_mappable_scancode_harvests(self): + harvest_1 = CDitem.objects.create(path='sourcearchive/mavencentral/io.nats/jnats/revision/2.6.6/tool/scancode/3.2.2.json') + harvest_2 = CDitem.objects.create( + path='sourcearchive/mavencentral/io.cucumber/cucumber-core/revision/5.0.0-RC1/tool/scancode/3.2.2.json', + last_map_date=timezone.now(), + map_error='error' + ) + definition_1 = CDitem.objects.create(path='sourcearchive/mavencentral/io.nats/jnats/revision/2.6.6.json') + mappables = list(CDitem.objects.mappable_scancode_harvests()) + self.assertEqual(1, len(mappables)) + harvest = mappables[0] + self.assertEqual(harvest_1, harvest) diff --git a/clearcode-toolkit/src/clearcode/tests/test_sync.py b/clearcode-toolkit/src/clearcode/tests/test_sync.py new file mode 100644 index 00000000..83bb7ce8 --- /dev/null +++ b/clearcode-toolkit/src/clearcode/tests/test_sync.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# +# ClearCode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import gzip +import json + +from django.test import TestCase +from django.utils import timezone + +from clearcode.models import CDitem +from clearcode.sync import db_saver + + +class SyncDbsaverTestCase(TestCase): + def setUp(self): + self.test_path = 'composer/packagist/yoast/wordpress-seo/revision/9.5-RC3.json' + self.test_content = {'test': 'content'} + + self.cditem0 = CDitem.objects.create( + path=self.test_path, + content=gzip.compress(json.dumps(self.test_content).encode('utf-8')), + ) + + def test_db_saver_identical_path(self): + db_saver(content=self.test_content, blob_path=self.test_path) + self.assertEqual(1, len(CDitem.objects.all())) + + def test_db_saver_different_path(self): + db_saver(content=self.test_content, blob_path='new/blob/path.json') + self.assertEqual(2, len(CDitem.objects.all())) diff --git a/matchcode/Makefile b/matchcode/Makefile index 6ce4a7de..dbadb4ca 100644 --- a/matchcode/Makefile +++ b/matchcode/Makefile @@ -102,6 +102,15 @@ test: shell: ${MANAGE} shell +clearsync: + ${ACTIVATE} clearsync --save-to-db --verbose -n 3 + +clearindex: + ${MANAGE} run_clearindex + +index_packages: + ${MANAGE} index_packages + bump: @echo "-> Bump the version" bin/bumpver update --no-fetch --patch diff --git a/matchcode/configure b/matchcode/configure index c04c077f..1d46c535 100755 --- a/matchcode/configure +++ b/matchcode/configure @@ -27,12 +27,12 @@ CLI_ARGS=$1 # Defaults. Change these variables to customize this script ################################ -CUSTOM_PACKAGES="https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip https://github.com/nexB/clearcode-toolkit/archive/e073e95878b375ec92d817a62c65d963408ff248.zip" +CUSTOM_PACKAGES="https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip" # Requirement arguments passed to pip and used by default or with --dev. -REQUIREMENTS="--editable ../packagedb --editable ../minecode --editable . --constraint requirements.txt $CUSTOM_PACKAGES" -DEV_REQUIREMENTS="--editable ../packagedb[testing] --editable ../minecode[testing] --editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt $CUSTOM_PACKAGES" -DOCS_REQUIREMENTS="--editable ../packagedb[docs] --editable ../minecode[docs] --editable .[docs] --constraint requirements.txt $CUSTOM_PACKAGES" +REQUIREMENTS="$CUSTOM_PACKAGES --editable ../clearcode-toolkit --editable ../packagedb --editable ../minecode --editable . --constraint requirements.txt" +DEV_REQUIREMENTS="$CUSTOM_PACKAGES --editable ../clearcode-toolkit --editable ../packagedb[testing] --editable ../minecode[testing] --editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt" +DOCS_REQUIREMENTS="$CUSTOM_PACKAGES --editable ../clearcode-toolkit --editable ../packagedb[docs] --editable ../minecode[docs] --editable .[docs] --constraint requirements.txt" # where we create a virtualenv VIRTUALENV_DIR=venv diff --git a/matchcode/requirements.txt b/matchcode/requirements.txt index ae091e6c..7e27df4d 100644 --- a/matchcode/requirements.txt +++ b/matchcode/requirements.txt @@ -57,7 +57,7 @@ pluggy==1.0.0 plugincode==31.0.0 ply==3.11 psycopg2==2.9.3 -psycopg2-binary==2.9.5 +psycopg2-binary==2.9.3 publicsuffix2==2.20191221 pyahocorasick==2.0.0b1 pycparser==2.21 diff --git a/matchcode/setup.cfg b/matchcode/setup.cfg index 03928ee9..29d2e294 100644 --- a/matchcode/setup.cfg +++ b/matchcode/setup.cfg @@ -31,7 +31,7 @@ include_package_data = true zip_safe = false install_requires = bitarray == 2.6.0 - clearcode==0.0.3 + clearcode-toolkit==0.0.3 commoncode == 31.0.0 Django == 4.1.2 django-environ==0.9.0 @@ -60,10 +60,3 @@ docs= Sphinx>=3.3.1 sphinx-rtd-theme>=0.5.0 doc8>=0.8.1 - -[options.entry_points] -scancode_scan = - halo1 = matchcode.plugin_fingerprint:Halo1Scanner - -scancodeio_pipelines = - matching = matchcode.pipelines.matching:Matching diff --git a/matchcode/src/matchcodeio/settings/__init__.py b/matchcode/src/matchcodeio/settings.py similarity index 99% rename from matchcode/src/matchcodeio/settings/__init__.py rename to matchcode/src/matchcodeio/settings.py index bef62831..c6d51c71 100644 --- a/matchcode/src/matchcodeio/settings/__init__.py +++ b/matchcode/src/matchcodeio/settings.py @@ -14,7 +14,7 @@ PROJECT_DIR = Path(__file__).resolve().parent.parent -ROOT_DIR = PROJECT_DIR.parent.parent +ROOT_DIR = PROJECT_DIR.parent # Environment @@ -55,6 +55,7 @@ # Must come before Third-party apps for proper templates override 'matchcode', 'packagedb', + 'discovery', 'clearindex', 'clearcode', # Django built-in diff --git a/minecode/Makefile b/minecode/Makefile index 6a8bfe83..71e4483a 100644 --- a/minecode/Makefile +++ b/minecode/Makefile @@ -95,6 +95,12 @@ test: @echo "-> Run the test suite" ${ACTIVATE} ${PYTHON_EXE} -m pytest -vvs +clearsync: + ${ACTIVATE} clearsync --save-to-db --verbose -n 3 + +clearindex: + ${MANAGE} run_clearindex + bump: @echo "-> Bump the version" bin/bumpver update --no-fetch --patch diff --git a/minecode/configure b/minecode/configure index 2dd7b18a..1b8f307e 100755 --- a/minecode/configure +++ b/minecode/configure @@ -30,9 +30,9 @@ CLI_ARGS=$1 CUSTOM_PACKAGES="https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip https://github.com/nexB/clearcode-toolkit/archive/e073e95878b375ec92d817a62c65d963408ff248.zip" # Requirement arguments passed to pip and used by default or with --dev. -REQUIREMENTS="--editable ../packagedb --editable . --constraint requirements.txt $CUSTOM_PACKAGES" -DEV_REQUIREMENTS="--editable ../packagedb[testing] --editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt $CUSTOM_PACKAGES" -DOCS_REQUIREMENTS="--editable ../packagedb[docs] --editable .[docs] --constraint requirements.txt $CUSTOM_PACKAGES" +REQUIREMENTS="$CUSTOM_PACKAGES --editable ../clearcode-toolkit --editable ../packagedb --editable . --constraint requirements.txt" +DEV_REQUIREMENTS="$CUSTOM_PACKAGES --editable ../clearcode-toolkit --editable ../packagedb[testing] --editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt" +DOCS_REQUIREMENTS="$CUSTOM_PACKAGES --editable ../clearcode-toolkit --editable ../packagedb[docs] --editable .[docs] --constraint requirements.txt" # where we create a virtualenv VIRTUALENV_DIR=venv diff --git a/minecode/setup.cfg b/minecode/setup.cfg index 49ea8bfc..39ba828a 100644 --- a/minecode/setup.cfg +++ b/minecode/setup.cfg @@ -41,7 +41,7 @@ include_package_data = true zip_safe = false install_requires = arrow==1.2.3 - clearcode==0.0.3 + clearcode-toolkit==0.0.3 debian-inspector==31.0.0 Django==4.1.2 django-filter==22.1 diff --git a/minecode/src/minecodeio/settings/base.py b/minecode/src/minecodeio/settings/base.py index d4fb105b..47ce4ad2 100644 --- a/minecode/src/minecodeio/settings/base.py +++ b/minecode/src/minecodeio/settings/base.py @@ -55,14 +55,14 @@ # Database DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'packagedb', 'USER': 'packagedb', 'PASSWORD': 'packagedb', - 'HOST': '127.0.0.1', + 'HOST': 'localhost', 'PORT': '5432', 'ATOMIC_REQUESTS': True, - }, + } } REST_FRAMEWORK = { From 6dcda34adc98d1f3caa97390bfd3efe411d0f077 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Mon, 5 Dec 2022 23:43:21 +0000 Subject: [PATCH 06/38] Use PackageDB envvars in matchcode Signed-off-by: Jono Yang --- matchcode/Makefile | 10 +++++----- matchcode/src/matchcodeio/settings.py | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/matchcode/Makefile b/matchcode/Makefile index dbadb4ca..646aa250 100644 --- a/matchcode/Makefile +++ b/matchcode/Makefile @@ -17,8 +17,8 @@ VIRTUALENV_PYZ=../etc/thirdparty/virtualenv.pyz GET_SECRET_KEY=`base64 /dev/urandom | head -c50` # Customize with `$ make envfile ENV_FILE=/etc/purldb/.env` ENV_FILE=.env -# Customize with `$ make postgres MATCHCODEIO_DB_PASSWORD=YOUR_PASSWORD` -MATCHCODEIO_DB_PASSWORD=packagedb +# Customize with `$ make postgres PACKAGEDB_DB_PASSWORD=YOUR_PASSWORD` +PACKAGEDB_DB_PASSWORD=packagedb # Use sudo for postgres, but only on Linux UNAME := $(shell uname) @@ -48,8 +48,8 @@ envfile: envfile-ci: envfile @echo "-> Add Azure Pipelines Postgres DB name and password to .env" - @echo MATCHCODEIO_DB_USER="postgres" >> ${ENV_FILE} - @echo MATCHCODEIO_DB_PASSWORD="postgres" >> ${ENV_FILE} + @echo PACKAGEDB_DB_USER="postgres" >> ${ENV_FILE} + @echo PACKAGEDB_DB_PASSWORD="postgres" >> ${ENV_FILE} isort: @echo "-> Apply isort changes to ensure proper imports ordering" @@ -85,7 +85,7 @@ postgres: @echo "-> Configure PostgreSQL database" @echo "-> Create database user 'packagedb'" ${SUDO_POSTGRES} createuser --no-createrole --no-superuser --login --inherit --createdb packagedb || true - ${SUDO_POSTGRES} psql -c "alter user packagedb with encrypted password '${MATCHCODEIO_DB_PASSWORD}';" || true + ${SUDO_POSTGRES} psql -c "alter user packagedb with encrypted password '${PACKAGEDB_DB_PASSWORD}';" || true @echo "-> Drop 'packagedb' database" ${SUDO_POSTGRES} dropdb packagedb || true @echo "-> Create 'packagedb' database" diff --git a/matchcode/src/matchcodeio/settings.py b/matchcode/src/matchcodeio/settings.py index c6d51c71..1d050171 100644 --- a/matchcode/src/matchcodeio/settings.py +++ b/matchcode/src/matchcodeio/settings.py @@ -89,12 +89,12 @@ DATABASES = { 'default': { - 'ENGINE': env.str('MATCHCODEIO_DB_ENGINE', 'django.db.backends.postgresql'), - 'HOST': env.str('MATCHCODEIO_DB_HOST', 'localhost'), - 'NAME': env.str('MATCHCODEIO_DB_NAME', 'packagedb'), - 'USER': env.str('MATCHCODEIO_DB_USER', 'packagedb'), - 'PASSWORD': env.str('MATCHCODEIO_DB_PASSWORD', 'packagedb'), - 'PORT': env.str('MATCHCODEIO_DB_PORT', '5432'), + 'ENGINE': env.str('PACKAGEDB_DB_ENGINE', 'django.db.backends.postgresql'), + 'HOST': env.str('PACKAGEDB_DB_HOST', 'localhost'), + 'NAME': env.str('PACKAGEDB_DB_NAME', 'packagedb'), + 'USER': env.str('PACKAGEDB_DB_USER', 'packagedb'), + 'PASSWORD': env.str('PACKAGEDB_DB_PASSWORD', 'packagedb'), + 'PORT': env.str('PACKAGEDB_DB_PORT', '5432'), 'ATOMIC_REQUESTS': True, } } From a367678c1e2855e1d81de4ab17088e942a6adb1c Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Mon, 5 Dec 2022 23:48:43 +0000 Subject: [PATCH 07/38] Add github CI for matchcode and clearcode tests * Use specific branch of commoncode with fix Signed-off-by: Jono Yang --- .github/workflows/clearcode-tests.yml | 53 ++++++++++++++++++++++++++ .github/workflows/matchcode-tests.yml | 54 +++++++++++++++++++++++++++ matchcode/configure | 2 +- 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/clearcode-tests.yml create mode 100644 .github/workflows/matchcode-tests.yml diff --git a/.github/workflows/clearcode-tests.yml b/.github/workflows/clearcode-tests.yml new file mode 100644 index 00000000..c6f70171 --- /dev/null +++ b/.github/workflows/clearcode-tests.yml @@ -0,0 +1,53 @@ +name: Clearcode Tests CI + +on: [push, pull_request] + +env: + POSTGRES_DB: packagedb + POSTGRES_USER: packagedb + POSTGRES_PASSWORD: packagedb + POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8 + +jobs: + build: + runs-on: ubuntu-20.04 + + services: + postgres: + image: postgres:13 + env: + POSTGRES_DB: ${{ env.POSTGRES_DB }} + POSTGRES_USER: ${{ env.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }} + POSTGRES_INITDB_ARGS: ${{ env.POSTGRES_INITDB_ARGS }} + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + strategy: + max-parallel: 4 + matrix: + python-version: ["3.8", "3.9", "3.10"] + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + working-directory: ./clearcode-toolkit + run: | + ./configure + + - name: Run tests + working-directory: ./clearcode-toolkit + run: | + python manage.py test clearcode --verbosity=2 diff --git a/.github/workflows/matchcode-tests.yml b/.github/workflows/matchcode-tests.yml new file mode 100644 index 00000000..49277813 --- /dev/null +++ b/.github/workflows/matchcode-tests.yml @@ -0,0 +1,54 @@ +name: Matchcode Tests CI + +on: [push, pull_request] + +env: + POSTGRES_DB: packagedb + POSTGRES_USER: packagedb + POSTGRES_PASSWORD: packagedb + POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8 + +jobs: + build: + runs-on: ubuntu-20.04 + + services: + postgres: + image: postgres:13 + env: + POSTGRES_DB: ${{ env.POSTGRES_DB }} + POSTGRES_USER: ${{ env.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }} + POSTGRES_INITDB_ARGS: ${{ env.POSTGRES_INITDB_ARGS }} + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + strategy: + max-parallel: 4 + matrix: + python-version: ["3.8", "3.9", "3.10"] + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + working-directory: ./matchcode + run: | + make dev + + - name: Run tests + working-directory: ./matchcode + run: | + make envfile + make test diff --git a/matchcode/configure b/matchcode/configure index 1d46c535..18352055 100755 --- a/matchcode/configure +++ b/matchcode/configure @@ -27,7 +27,7 @@ CLI_ARGS=$1 # Defaults. Change these variables to customize this script ################################ -CUSTOM_PACKAGES="https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip" +CUSTOM_PACKAGES="https://github.com/nexB/commoncode/archive/refs/heads/48-correctly-assign-codebase-attributes.zip https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip" # Requirement arguments passed to pip and used by default or with --dev. REQUIREMENTS="$CUSTOM_PACKAGES --editable ../clearcode-toolkit --editable ../packagedb --editable ../minecode --editable . --constraint requirements.txt" From d296a9993a7e3fef45774a0d0b948a18e45fdc42 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Tue, 6 Dec 2022 01:24:35 +0000 Subject: [PATCH 08/38] Activate venv in clearcode test CI * Do not install clearcode from repo archive in minecode configure Signed-off-by: Jono Yang --- .github/workflows/clearcode-tests.yml | 1 + minecode/configure | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/clearcode-tests.yml b/.github/workflows/clearcode-tests.yml index c6f70171..304a4376 100644 --- a/.github/workflows/clearcode-tests.yml +++ b/.github/workflows/clearcode-tests.yml @@ -50,4 +50,5 @@ jobs: - name: Run tests working-directory: ./clearcode-toolkit run: | + source bin/activate python manage.py test clearcode --verbosity=2 diff --git a/minecode/configure b/minecode/configure index 1b8f307e..c303c884 100755 --- a/minecode/configure +++ b/minecode/configure @@ -27,7 +27,7 @@ CLI_ARGS=$1 # Defaults. Change these variables to customize this script ################################ -CUSTOM_PACKAGES="https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip https://github.com/nexB/clearcode-toolkit/archive/e073e95878b375ec92d817a62c65d963408ff248.zip" +CUSTOM_PACKAGES="https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip" # Requirement arguments passed to pip and used by default or with --dev. REQUIREMENTS="$CUSTOM_PACKAGES --editable ../clearcode-toolkit --editable ../packagedb --editable . --constraint requirements.txt" From db2fa69bda771952f940ae2ddf05fd90687dc06d Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Tue, 6 Dec 2022 01:59:24 +0000 Subject: [PATCH 09/38] Update comands in matchcode README Signed-off-by: Jono Yang --- matchcode/README.rst | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/matchcode/README.rst b/matchcode/README.rst index 0163bf55..0ee4aa75 100644 --- a/matchcode/README.rst +++ b/matchcode/README.rst @@ -8,42 +8,46 @@ Installation Requirements ############ * Debian-based Linux distribution -* Python 3.6 -* Postgres 10 +* Python 3.9 or later +* Postgres 13 * git -* PackageDB (https://github.com/nexB/packagedb) -* ClearCode Toolkit (https://github.com/nexB/clearcode-toolkit) +* scancode-toolkit runtime dependencies (https://scancode-toolkit.readthedocs.io/en/stable/getting-started/install.html#install-prerequisites) Once the prerequisites have been installed, set up MatchCode with the following commands: :: git clone https://github.com/nexB/matchcode.git cd matchcode - source configure - sudo -u postgres createuser --no-createrole --no-superuser --login --inherit --createdb --pwprompt matchcode - sudo -u postgres createdb --encoding=utf-8 matchcode --owner=matchcode - # update your local DB - python manage.py migrate + ./configure --dev + make postgres Once MatchCode and the database has been set up, run tests to ensure functionality: :: - python manage.py test + make test + Post-Installation ----------------- If you have an empty PackageDB without Package and Package Resource information, ClearCode Toolkit should be run for a while so it can populate the PackageDB with Package and Package Resource information from clearlydefined. +:: + make clearsync + +After some ClearlyDefined harvests and definitions have been obtained, run ``clearindex`` to create Packages and Resources from the harvests and definitions. +:: + make clearindex + The Package and Package Resource information will be used to create the matching indices. Once the PackageDB has been populated, run the following command to create the matching indices from the collected Package data: :: - python manage.py index_packages + make index_packages Usage ----- Start the MatchCode server by running: :: - python manage.py runserver + make run You can send a ScanCode JSON scan for matching at the api/match_request/ endpoint using the HTML view or API. From d4476007d6f190b7f5a69cb5e60b314b24867b4f Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Tue, 6 Dec 2022 02:14:13 +0000 Subject: [PATCH 10/38] Correctly determine project and root dirs in settings * Create minecodeio/static dir Signed-off-by: Jono Yang --- matchcode/src/matchcodeio/settings.py | 4 ++-- matchcode/src/matchcodeio/static/.keep | 0 packagedb/src/packagedbio/settings.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 matchcode/src/matchcodeio/static/.keep diff --git a/matchcode/src/matchcodeio/settings.py b/matchcode/src/matchcodeio/settings.py index 1d050171..cb5796d3 100644 --- a/matchcode/src/matchcodeio/settings.py +++ b/matchcode/src/matchcodeio/settings.py @@ -13,8 +13,8 @@ import environ -PROJECT_DIR = Path(__file__).resolve().parent.parent -ROOT_DIR = PROJECT_DIR.parent +PROJECT_DIR = Path(__file__).resolve().parent +ROOT_DIR = PROJECT_DIR.parent.parent # Environment diff --git a/matchcode/src/matchcodeio/static/.keep b/matchcode/src/matchcodeio/static/.keep new file mode 100644 index 00000000..e69de29b diff --git a/packagedb/src/packagedbio/settings.py b/packagedb/src/packagedbio/settings.py index 3df50929..da14f91e 100644 --- a/packagedb/src/packagedbio/settings.py +++ b/packagedb/src/packagedbio/settings.py @@ -12,8 +12,8 @@ import environ -PROJECT_DIR = Path(__file__).resolve().parent.parent -ROOT_DIR = PROJECT_DIR.parent +PROJECT_DIR = Path(__file__).resolve().parent +ROOT_DIR = PROJECT_DIR.parent.parent # Environment From 718a3df3f6e2153f92928520087017477cf3d259 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Tue, 6 Dec 2022 02:16:15 +0000 Subject: [PATCH 11/38] Update commands in README.rst Signed-off-by: Jono Yang --- matchcode/README.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/matchcode/README.rst b/matchcode/README.rst index 0ee4aa75..4f5cccd1 100644 --- a/matchcode/README.rst +++ b/matchcode/README.rst @@ -16,10 +16,11 @@ Requirements Once the prerequisites have been installed, set up MatchCode with the following commands: :: - git clone https://github.com/nexB/matchcode.git - cd matchcode + git clone https://github.com/nexb/purldb + cd purldb/matchcode ./configure --dev make postgres + make envfile Once MatchCode and the database has been set up, run tests to ensure functionality: :: From f5f911f504aaaa4c44e4565a2c62df091a89c5a3 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Tue, 6 Dec 2022 02:35:05 +0000 Subject: [PATCH 12/38] Update Makefile to use configure commands * Update minecode test expectations Signed-off-by: Jono Yang --- matchcode/Makefile | 7 +------ minecode/Makefile | 18 ++++++++++-------- minecode/setup.cfg | 1 + .../mapper/xbean-jmx-2.0.pom.package.json | 8 ++++---- ...-docs-maven-plugin-1.0-RC1.pom.package.json | 2 +- packagedb/Makefile | 18 ++++++++++-------- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/matchcode/Makefile b/matchcode/Makefile index 646aa250..b0c8d981 100644 --- a/matchcode/Makefile +++ b/matchcode/Makefile @@ -46,11 +46,6 @@ envfile: @mkdir -p $(shell dirname ${ENV_FILE}) && touch ${ENV_FILE} @echo SECRET_KEY=\"${GET_SECRET_KEY}\" > ${ENV_FILE} -envfile-ci: envfile - @echo "-> Add Azure Pipelines Postgres DB name and password to .env" - @echo PACKAGEDB_DB_USER="postgres" >> ${ENV_FILE} - @echo PACKAGEDB_DB_PASSWORD="postgres" >> ${ENV_FILE} - isort: @echo "-> Apply isort changes to ensure proper imports ordering" ${VENV}/bin/isort . @@ -128,4 +123,4 @@ docker-images: @mkdir -p dist/ @docker save minecode minecode_minecode nginx | gzip > dist/minecode-images-`git describe --tags`.tar.gz -.PHONY: virtualenv conf dev envfile install check valid isort clean migrate postgres sqlite run test bump docs docker-images +.PHONY: virtualenv conf dev envfile isort black doc8 valid check clean migrate postgres run test shell clearsync clearindex index_packages bump docs docker-images diff --git a/minecode/Makefile b/minecode/Makefile index 71e4483a..9514ddc6 100644 --- a/minecode/Makefile +++ b/minecode/Makefile @@ -32,13 +32,13 @@ virtualenv: @echo "-> Bootstrap the virtualenv with PYTHON_EXE=${PYTHON_EXE}" @${PYTHON_EXE} ${VIRTUALENV_PYZ} --never-download --no-periodic-update ${VENV} -conf: virtualenv +conf: @echo "-> Install dependencies" - @${ACTIVATE} pip install -e . -c requirements.txt + @./configure -dev: virtualenv +dev: @echo "-> Configure and install development dependencies" - @${ACTIVATE} pip install -e .[dev] -c requirements.txt + @./configure --dev envfile: @echo "-> Create the .env file and generate a secret key" @@ -70,8 +70,7 @@ check: clean: @echo "-> Clean the Python env" - rm -rf ${VENV} build/ dist/ packagedb.egg-info/ docs/_build/ pip-selfcheck.json - find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete + @./configure --clean migrate: @echo "-> Apply database migrations" @@ -93,7 +92,10 @@ run: test: @echo "-> Run the test suite" - ${ACTIVATE} ${PYTHON_EXE} -m pytest -vvs + ${ACTIVATE} DJANGO_SETTINGS_MODULE=minecodeio.settings ${PYTHON_EXE} -m pytest -vvs + +shell: + ${MANAGE} shell clearsync: ${ACTIVATE} clearsync --save-to-db --verbose -n 3 @@ -118,4 +120,4 @@ docker-images: @mkdir -p dist/ @docker save postgres packagedb_packagedb nginx | gzip > dist/packagedb-images-`git describe --tags`.tar.gz -.PHONY: virtualenv conf dev envfile install check valid isort clean migrate postgres sqlite run test bump docs docker-images +.PHONY: virtualenv conf dev envfile isort black doc8 valid check clean migrate postgres run test shell clearsync clearindex bump docs docker-images diff --git a/minecode/setup.cfg b/minecode/setup.cfg index 39ba828a..201c8274 100644 --- a/minecode/setup.cfg +++ b/minecode/setup.cfg @@ -67,6 +67,7 @@ where = src testing = pytest >= 6, != 7.0.0 pytest-xdist >= 2 + pytest-django aboutcode-toolkit >= 6.0.0 black mock diff --git a/minecode/tests/discovery/testfiles/maven/mapper/xbean-jmx-2.0.pom.package.json b/minecode/tests/discovery/testfiles/maven/mapper/xbean-jmx-2.0.pom.package.json index c11da554..8135ecbd 100644 --- a/minecode/tests/discovery/testfiles/maven/mapper/xbean-jmx-2.0.pom.package.json +++ b/minecode/tests/discovery/testfiles/maven/mapper/xbean-jmx-2.0.pom.package.json @@ -36,7 +36,7 @@ "scope": "compile", "is_runtime": false, "is_optional": true, - "is_resolved": null, + "is_resolved": false, "resolved_package": {}, "extra_data": {} }, @@ -46,7 +46,7 @@ "scope": "compile", "is_runtime": false, "is_optional": true, - "is_resolved": null, + "is_resolved": false, "resolved_package": {}, "extra_data": {} }, @@ -56,7 +56,7 @@ "scope": "compile", "is_runtime": false, "is_optional": true, - "is_resolved": null, + "is_resolved": false, "resolved_package": {}, "extra_data": {} }, @@ -66,7 +66,7 @@ "scope": "compile", "is_runtime": false, "is_optional": true, - "is_resolved": null, + "is_resolved": false, "resolved_package": {}, "extra_data": {} } diff --git a/minecode/tests/discovery/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom.package.json b/minecode/tests/discovery/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom.package.json index 3a278602..16fd3ab3 100644 --- a/minecode/tests/discovery/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom.package.json +++ b/minecode/tests/discovery/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom.package.json @@ -36,7 +36,7 @@ "scope": "compile", "is_runtime": false, "is_optional": true, - "is_resolved": null, + "is_resolved": false, "resolved_package": {}, "extra_data": {} }, diff --git a/packagedb/Makefile b/packagedb/Makefile index 6a8bfe83..475e034c 100644 --- a/packagedb/Makefile +++ b/packagedb/Makefile @@ -32,13 +32,13 @@ virtualenv: @echo "-> Bootstrap the virtualenv with PYTHON_EXE=${PYTHON_EXE}" @${PYTHON_EXE} ${VIRTUALENV_PYZ} --never-download --no-periodic-update ${VENV} -conf: virtualenv +conf: @echo "-> Install dependencies" - @${ACTIVATE} pip install -e . -c requirements.txt + @./configure -dev: virtualenv +dev: @echo "-> Configure and install development dependencies" - @${ACTIVATE} pip install -e .[dev] -c requirements.txt + @./configure --dev envfile: @echo "-> Create the .env file and generate a secret key" @@ -70,8 +70,7 @@ check: clean: @echo "-> Clean the Python env" - rm -rf ${VENV} build/ dist/ packagedb.egg-info/ docs/_build/ pip-selfcheck.json - find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete + @./configure --clean migrate: @echo "-> Apply database migrations" @@ -93,7 +92,10 @@ run: test: @echo "-> Run the test suite" - ${ACTIVATE} ${PYTHON_EXE} -m pytest -vvs + ${ACTIVATE} DJANGO_SETTINGS_MODULE=packagedbio.settings ${PYTHON_EXE} -m pytest -vvs + +shell: + ${MANAGE} shell bump: @echo "-> Bump the version" @@ -112,4 +114,4 @@ docker-images: @mkdir -p dist/ @docker save postgres packagedb_packagedb nginx | gzip > dist/packagedb-images-`git describe --tags`.tar.gz -.PHONY: virtualenv conf dev envfile install check valid isort clean migrate postgres sqlite run test bump docs docker-images +.PHONY: virtualenv conf dev envfile isort black doc8 valid check clean migrate postgres run test shell bump docs docker-images From c4847e23ff3c43a7b8a960d8eb9fd5d326d7674b Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Tue, 6 Dec 2022 20:36:20 +0000 Subject: [PATCH 13/38] Add scancode matching plugin * Guard imports in matchcode.utils Signed-off-by: Jono Yang --- matchcode/setup.cfg | 4 + matchcode/src/matchcode/plugin_match.py | 195 ++++++++++++++++++++++++ matchcode/src/matchcode/utils.py | 4 +- 3 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 matchcode/src/matchcode/plugin_match.py diff --git a/matchcode/setup.cfg b/matchcode/setup.cfg index 29d2e294..65198076 100644 --- a/matchcode/setup.cfg +++ b/matchcode/setup.cfg @@ -60,3 +60,7 @@ docs= Sphinx>=3.3.1 sphinx-rtd-theme>=0.5.0 doc8>=0.8.1 + +[options.entry_points] +scancode_post_scan = + match = matchcode.plugin_match:Match diff --git a/matchcode/src/matchcode/plugin_match.py b/matchcode/src/matchcode/plugin_match.py new file mode 100644 index 00000000..23b63069 --- /dev/null +++ b/matchcode/src/matchcode/plugin_match.py @@ -0,0 +1,195 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# ScanCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/scancode-toolkit for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +from collections import defaultdict +import attr + +import requests + +from commoncode.cliutils import PluggableCommandLineOption +from commoncode.cliutils import POST_SCAN_GROUP +from matchcode.fingerprinting import compute_directory_fingerprints +from matchcode.utils import path_suffixes +from plugincode.post_scan import post_scan_impl +from plugincode.post_scan import PostScanPlugin + + +MATCHCODE_ENDPOINT = 'http://127.0.0.1:8001/api/approximate_directory_content_index/match/' +PACKAGEDB_ENDPOINT = 'http://127.0.0.1:8001/api/packages/' +PACKAGEDB_RESOURCES_ENDPOINT = 'http://127.0.0.1:8001/api/resources/' + + +class PackageInfo: + def __init__(self, purl): + self.purl = purl + self.package_resources = self.get_resources_from_packagedb(purl) + self.package_resource_by_paths = self.create_package_resource_by_paths() + + @classmethod + def get_resources_from_packagedb(cls, purl): + # Get package resources + payload = { + 'purl': purl + } + package_resources = [] + response = requests.get(PACKAGEDB_RESOURCES_ENDPOINT, params=payload) + while response: + jsoned_response = response.json() + package_resources.extend(jsoned_response.get('results', [])) + next_page = jsoned_response.get('next') + if not next_page: + break + response = requests.get(next_page) + return package_resources + + def create_package_resource_by_paths(self): + return { + package_resource.get('path'): package_resource + for package_resource in self.package_resources + } + + +def check_resource_path(resource, package_resources_by_path): + """ + Check to see if `resource` exists in the set of package Resources + `package_resources_by_path` + """ + for path_suffix in path_suffixes(resource.path): + if not path_suffix in package_resources_by_path: + continue + package_resource = package_resources_by_path[path_suffix] + # Check to see if we have the same Resource + if ((resource.is_file == True + and package_resource.get('is_file') == True + and resource.sha1 == package_resource.get('sha1', '')) + or (resource.is_file == False + and package_resource.get('is_file') == False)): + return True + return False + + +def determine_best_package_match(directory, codebase, package_info_by_purl): + """ + For all potential package matches in `package_info_by_purl`, return the + package whose codebase structure matches ours the most. + """ + # Calculate the percent of package files found in codebase + purls_by_match_ratio = {} + matched_codebase_paths_by_purl = defaultdict(list) + for matched_package_purl, package_info in package_info_by_purl.items(): + matched_codebase_paths = matched_codebase_paths_by_purl[matched_package_purl] + package_resource_by_paths = package_info.package_resource_by_paths + + # TODO: Theres a problem when try to match the directory with + # the name `package` because on the index side, we have the path + # `package` indexed, but the path suffixes function only returns + # paths that are at least two segments long + # + # We get around this by checking filetype (file or directory) in `check_resource_path` + if check_resource_path(directory, package_resource_by_paths): + matched_codebase_paths.append(directory.path) + + for child in directory.walk(codebase, topdown=True): + if check_resource_path(child, package_resource_by_paths): + matched_codebase_paths.append(child.path) + + matching_resources_count = len(matched_codebase_paths) + ratio = matching_resources_count / len(package_resource_by_paths) + purls_by_match_ratio[ratio] = matched_package_purl + + highest_match_ratio = max(match_ratio for match_ratio, _ in purls_by_match_ratio.items()) + best_package_match_purl = purls_by_match_ratio[highest_match_ratio] + return best_package_match_purl, matched_codebase_paths_by_purl[best_package_match_purl] + + +@post_scan_impl +class Match(PostScanPlugin): + codebase_attributes = dict( + # a list of matches + matches=attr.ib(default=attr.Factory(list), repr=False), + ) + resource_attributes = dict( + # a list of purls of the packages that a file is a part of + matched_to=attr.ib(default=attr.Factory(list), repr=False), + ) + + sort_order = 6 + + options = [ + PluggableCommandLineOption( + ( + '-m', + '--match', + ), + is_flag=True, + default=False, + help='Scan for application package and dependency manifests, lockfiles and related data.', + help_group=POST_SCAN_GROUP, + sort_order=20, + ) + ] + + def is_enabled(self, match, **kwargs): + return match + + def process_codebase(self, codebase, **kwargs): + codebase = compute_directory_fingerprints(codebase) + for resource in codebase.walk(topdown=True): + # Collect directory fingerprints, if available + directory_content_fingerprint = resource.extra_data.get('directory_content', '') + + # Skip resource if it is not a directory, does not contain directory + # fingerprints, or if it has already been matched + if (resource.is_file + or not directory_content_fingerprint + or resource.extra_data.get('matched', False)): + continue + + # Send fingerprint to matchcode for matching and get the purls of + # the matched packages + payload = { + 'fingerprint': [directory_content_fingerprint] + } + response = requests.get(MATCHCODE_ENDPOINT, params=payload) + if response: + results = response.json() + matched_purls = [result.get('purl', '') for result in results] + if not matched_purls: + continue + + # Get the paths of the resources from matched packages + package_info_by_purl = {} + for purl in matched_purls: + package_info_by_purl[purl] = PackageInfo(purl) + + # Calculate the percent of package files found in codebase + best_package_match_purl, matched_codebase_paths = determine_best_package_match(resource, codebase, package_info_by_purl) + + # Query PackageDB for info on the best matched package + payload = { + 'purl': best_package_match_purl + } + response = requests.get(PACKAGEDB_ENDPOINT, params=payload) + if response: + # Create DiscoveredPackage for the best matched package + packagedb_results = response.json().get('results', []) + for package in packagedb_results: + package.pop('uuid') + if package in codebase.attributes.matches: + continue + codebase.attributes.matches.append(package) + + # Associate the package to the resource and its children + for matched_codebase_path in matched_codebase_paths: + r = codebase.get_resource(matched_codebase_path) + if best_package_match_purl in r.matched_to: + continue + r.matched_to.append(best_package_match_purl) + r.extra_data['matched'] = True + r.save(codebase) diff --git a/matchcode/src/matchcode/utils.py b/matchcode/src/matchcode/utils.py index 15dd92f6..8d3c381a 100644 --- a/matchcode/src/matchcode/utils.py +++ b/matchcode/src/matchcode/utils.py @@ -20,8 +20,6 @@ from django.test import TestCase as DjangoTestCase from commoncode.resource import VirtualCodebase -from packagedb.models import Package -from packagedb.models import Resource ############## TEST UTILITIES ############## @@ -98,6 +96,7 @@ def to_os_native_path(path): def load_resources_from_scan(scan_location, package): + from packagedb.models import Resource vc = VirtualCodebase( location=scan_location, ) @@ -118,6 +117,7 @@ def index_packages_sha1(): """ from matchcode.models import ExactPackageArchiveIndex from matchcode.models import get_or_create_indexable_package + from packagedb.models import Package for package in Package.objects.filter(sha1__isnull=False): indexable_package, _ = get_or_create_indexable_package(package) From 528583296e608a25767c714eec1e63d0e86bef23 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Wed, 7 Dec 2022 18:24:38 -0800 Subject: [PATCH 14/38] Remove IndexablePackage model * Add index_error and last_indexed_date to Package model in PackageDB Signed-off-by: Jono Yang --- matchcode/src/matchcode/api.py | 6 +- matchcode/src/matchcode/indexing.py | 46 +++++------ .../management/commands/index_packages.py | 18 +---- matchcode/src/matchcode/match.py | 4 +- ...edirectorycontentindex_package_and_more.py | 54 +++++++++++++ matchcode/src/matchcode/models.py | 80 ++++--------------- matchcode/src/matchcode/utils.py | 8 +- matchcode/tests/matchcode/test_api.py | 7 +- .../tests/matchcode/test_index_packages.py | 65 +++++---------- matchcode/tests/matchcode/test_match.py | 34 +++----- matchcode/tests/matchcode/test_models.py | 18 ++--- minecode/src/clearindex/harvest.py | 3 +- .../management/commands/run_clearindex.py | 40 +++++----- .../discovery/management/commands/run_map.py | 2 +- ...e_index_error_package_last_indexed_date.py | 31 +++++++ packagedb/src/packagedb/models.py | 11 ++- 16 files changed, 205 insertions(+), 222 deletions(-) create mode 100644 matchcode/src/matchcode/migrations/0002_alter_approximatedirectorycontentindex_package_and_more.py create mode 100644 packagedb/src/packagedb/migrations/0052_package_index_error_package_last_indexed_date.py diff --git a/matchcode/src/matchcode/api.py b/matchcode/src/matchcode/api.py index f8496a23..3a27e9b1 100644 --- a/matchcode/src/matchcode/api.py +++ b/matchcode/src/matchcode/api.py @@ -31,7 +31,7 @@ class BaseFileIndexSerializer(ModelSerializer): sha1 = CharField(source='fingerprint') - purl = CharField(source='package.package.package_url') + purl = CharField(source='package.package_url') class ExactFileIndexSerializer(BaseFileIndexSerializer): @@ -54,7 +54,7 @@ class Meta: class BaseDirectoryIndexSerializer(ModelSerializer): fingerprint = ReadOnlyField() - purl = CharField(source='package.package.package_url') + purl = CharField(source='package.package_url') class ApproximateDirectoryContentIndexSerializer(BaseDirectoryIndexSerializer): @@ -241,7 +241,7 @@ def match(self, request): { 'fingerprint': fingerprint, 'matched_fingerprint': match.fingerprint(), - 'purl': match.package.package.package_url, + 'purl': match.package.package_url, } ) diff --git a/matchcode/src/matchcode/indexing.py b/matchcode/src/matchcode/indexing.py index 3526da7f..27604d02 100644 --- a/matchcode/src/matchcode/indexing.py +++ b/matchcode/src/matchcode/indexing.py @@ -16,7 +16,6 @@ from matchcode.fingerprinting import compute_directory_fingerprints from matchcode.models import ApproximateDirectoryContentIndex from matchcode.models import ApproximateDirectoryStructureIndex -from matchcode.models import get_or_create_indexable_package from matchcode.models import ExactPackageArchiveIndex from matchcode.models import ExactFileIndex @@ -28,18 +27,16 @@ logger.setLevel(logging.INFO) -def index_package_archives(indexable_package): +def index_package_archives(package): """ Index Package archives for matching Return True if an ExactPackageArchiveIndex has been created, otherwise return False """ - package_data = indexable_package.package - package_sha1 = package_data.sha1 _, created = ExactPackageArchiveIndex.index( - sha1=package_sha1, - indexable_package=indexable_package, + sha1=package.sha1, + package=package, ) return created @@ -48,26 +45,24 @@ def index_package_file(resource): """ Index Package files for matching - Return a tuple of booleans, `created_exact_file_index` and `created_indexable_package`. - `created_exact_file_index` returns True if it has been created, False otherwise. - The same is true with `created_indexable_package` + Return a boolean, `created_exact_file_index`, which returns True if it has + been created, False otherwise. """ - indexable_package, created_indexable_package = get_or_create_indexable_package(resource.package) _, created_exact_file_index = ExactFileIndex.index( sha1=resource.sha1, - indexable_package=indexable_package + package=resource.package ) - return created_exact_file_index, created_indexable_package + return created_exact_file_index -def _create_virtual_codebase_from_indexable_package(indexable_package): +def _create_virtual_codebase_from_package_resources(package): """ - Return a VirtualCodebase from the resources of `indexable_package` + Return a VirtualCodebase from the resources of `package` """ # Create something that looks like a scancode scan so we can import it into a VirtualCodebase # TODO: Evolve this into something more elaborate, e.g. # Codebase class methods can manipulate Resource table entries - package_resources = indexable_package.resources.order_by('path') + package_resources = package.resources.order_by('path') if not package_resources: return @@ -93,7 +88,6 @@ def _create_virtual_codebase_from_indexable_package(indexable_package): break if make_new_root: - package = indexable_package.package new_root = '{}-{}'.format(package.name, package.version) for f in files: new_path = os.path.join(new_root, f.get('path', '')) @@ -104,7 +98,7 @@ def _create_virtual_codebase_from_indexable_package(indexable_package): return VirtualCodebase(location=mock_scan) -def index_directory_fingerprints(codebase, indexable_package): +def index_directory_fingerprints(codebase, package): """ Compute fingerprints for a directory from `codebase` and index them to ApproximateDirectoryContentIndex and ApproximateDirectoryStructureIndex @@ -123,7 +117,7 @@ def index_directory_fingerprints(codebase, indexable_package): _, adci_created = ApproximateDirectoryContentIndex.index( directory_fingerprint=directory_content_fingerprint, resource_path=resource.path, - indexable_package=indexable_package, + package=package, ) if adci_created: indexed_adci += 1 @@ -132,7 +126,7 @@ def index_directory_fingerprints(codebase, indexable_package): _, adsi_created = ApproximateDirectoryStructureIndex.index( directory_fingerprint=directory_structure_fingerprint, resource_path=resource.path, - indexable_package=indexable_package, + package=package, ) if adsi_created: indexed_adsi += 1 @@ -140,21 +134,21 @@ def index_directory_fingerprints(codebase, indexable_package): return indexed_adci, indexed_adsi -def index_package_directories(indexable_package): +def index_package_directories(package): """ - Index the directories of `indexable_package` to - ApproximateDirectoryContentIndex and ApproximateDirectoryStructureIndex + Index the directories of `package` to ApproximateDirectoryContentIndex and + ApproximateDirectoryStructureIndex Return a tuple of integers, `indexed_adci` and `indexed_adsi`, that represent the number of indexed ApproximateDirectoryContentIndex and ApproximateDirectoryStructureIndex created, respectivly. - Return 0, 0 if a VirtualCodebase cannot be created from the Resources of an - IndexablePackage + Return 0, 0 if a VirtualCodebase cannot be created from the Resources of a + Package """ - vc = _create_virtual_codebase_from_indexable_package(indexable_package) + vc = _create_virtual_codebase_from_package_resources(package) if not vc: return 0, 0 vc = compute_directory_fingerprints(vc) - return index_directory_fingerprints(vc, indexable_package) + return index_directory_fingerprints(vc, package) diff --git a/matchcode/src/matchcode/management/commands/index_packages.py b/matchcode/src/matchcode/management/commands/index_packages.py index cebb2345..e17a1d8e 100644 --- a/matchcode/src/matchcode/management/commands/index_packages.py +++ b/matchcode/src/matchcode/management/commands/index_packages.py @@ -18,8 +18,6 @@ from matchcode.indexing import index_package_directories from matchcode.indexing import index_package_file from matchcode.management.commands import VerboseCommand -from matchcode.models import get_or_create_indexable_package -from matchcode.models import IndexablePackage from packagedb.models import Package from packagedb.models import Resource @@ -36,7 +34,6 @@ class Command(VerboseCommand): def handle(self, *args, **options): # Stats to keep track of during indexing - total_indexable_packages_created = 0 total_indexed_package_archives = 0 total_indexed_package_files = 0 total_indexed_adci = 0 @@ -48,26 +45,20 @@ def handle(self, *args, **options): packages = Package.objects.filter(sha1__isnull=False) for package in packages.iterator(): with transaction.atomic(): - indexable_package, created_indexable_package = get_or_create_indexable_package(package) - if created_indexable_package: - total_indexable_packages_created += 1 - created_package_archive = index_package_archives(indexable_package) + created_package_archive = index_package_archives(package) if created_package_archive: total_indexed_package_archives += 1 resources = Resource.objects.filter(sha1__isnull=False) for resource in resources.iterator(): with transaction.atomic(): - created_package_file, created_indexable_package = index_package_file(resource) + created_package_file = index_package_file(resource) if created_package_file: total_indexed_package_files += 1 - if created_indexable_package: - total_indexable_packages_created += 1 - indexable_packages = IndexablePackage.objects.all() - for indexable_package in indexable_packages.iterator(): + for package in Package.objects.all().iterator(): with transaction.atomic(): - indexed_adci, indexed_adsi = index_package_directories(indexable_package) + indexed_adci, indexed_adsi = index_package_directories(package) total_indexed_adci += indexed_adci total_indexed_adsi += indexed_adsi @@ -76,7 +67,6 @@ def handle(self, *args, **options): total_duration = int(time.time() - start) print('Total run duration: {} seconds'.format(total_duration)) print('Created:') - print('IndexablePackages: {}'.format(total_indexable_packages_created)) print('ExactPackageArchiveIndex: {}'.format(total_indexed_package_archives)) print('ExactFileIndex: {}'.format(total_indexed_package_files)) print('ApproximateDirectoryContentIndex: {}'.format(total_indexed_adci)) diff --git a/matchcode/src/matchcode/match.py b/matchcode/src/matchcode/match.py index a0db2faa..e78bc8e8 100644 --- a/matchcode/src/matchcode/match.py +++ b/matchcode/src/matchcode/match.py @@ -198,11 +198,11 @@ def tag_matched_resources(resource, codebase, matches, match_type): """ for match in matches: # Prep matched package data and append to `codebase` - matched_package_info = match.package.package.to_dict() + matched_package_info = match.package.to_dict() matched_package_info['match_type'] = match_type codebase.attributes.matches.append(matched_package_info) - purl = match.package.package.package_url + purl = match.package.package_url # Tag the Resource where we found a match tag_matched_resource(resource, codebase, purl) diff --git a/matchcode/src/matchcode/migrations/0002_alter_approximatedirectorycontentindex_package_and_more.py b/matchcode/src/matchcode/migrations/0002_alter_approximatedirectorycontentindex_package_and_more.py new file mode 100644 index 00000000..2d34ed5e --- /dev/null +++ b/matchcode/src/matchcode/migrations/0002_alter_approximatedirectorycontentindex_package_and_more.py @@ -0,0 +1,54 @@ +# Generated by Django 4.1.2 on 2022-12-08 01:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("packagedb", "0052_package_index_error_package_last_indexed_date"), + ("matchcode", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="approximatedirectorycontentindex", + name="package", + field=models.ForeignKey( + help_text="The Package that this directory is a part of", + on_delete=django.db.models.deletion.CASCADE, + to="packagedb.package", + ), + ), + migrations.AlterField( + model_name="approximatedirectorystructureindex", + name="package", + field=models.ForeignKey( + help_text="The Package that this directory is a part of", + on_delete=django.db.models.deletion.CASCADE, + to="packagedb.package", + ), + ), + migrations.AlterField( + model_name="exactfileindex", + name="package", + field=models.ForeignKey( + help_text="The Package that this file is from", + on_delete=django.db.models.deletion.CASCADE, + to="packagedb.package", + ), + ), + migrations.AlterField( + model_name="exactpackagearchiveindex", + name="package", + field=models.ForeignKey( + help_text="The Package that this file is from", + on_delete=django.db.models.deletion.CASCADE, + to="packagedb.package", + ), + ), + migrations.DeleteModel( + name="IndexablePackage", + ), + ] diff --git a/matchcode/src/matchcode/models.py b/matchcode/src/matchcode/models.py index 07761123..cbf8de30 100644 --- a/matchcode/src/matchcode/models.py +++ b/matchcode/src/matchcode/models.py @@ -45,53 +45,6 @@ def logger_debug(*args): return logger.debug(' '.join(isinstance(a, str) and a or repr(a) for a in args)) -class IndexablePackage(models.Model): - """ - This is a model that mirrors the existing packages on a PackageDB instance. - """ - uuid = models.UUIDField( - _('UUID'), - unique=True, - help_text='The UUID of a Package on an instance of PackageDB', - db_index=True, - ) - - last_indexed_date = models.DateTimeField( - help_text='Timestamp set to the date of the last indexing. Used to track indexing status.' - ) - - index_error = models.TextField( - null=True, - blank=True, - help_text='Indexing errors messages. When present this means the indexing has failed.', - ) - - @property - def package(self): - # TODO: have option to get data from API vs DB query - return Package.objects.get(uuid=self.uuid) - - @property - def resources(self): - # TODO: Return resources as Codebase - # TODO: have option to get data from API vs DB query - return Resource.objects.filter(package=self.package) - - -def get_or_create_indexable_package(package): - created = False - try: - # Check to see if we have an IndexablePackage for this package - indexable_package = IndexablePackage.objects.get(uuid=package.uuid) - except ObjectDoesNotExist: - indexable_package = IndexablePackage.objects.create( - uuid=package.uuid, - last_indexed_date = timezone.now() - ) - created = True - return indexable_package, created - - ############################################################################### # FILE MATCHING ############################################################################### @@ -105,7 +58,7 @@ class BaseFileIndex(models.Model): ) package = models.ForeignKey( - IndexablePackage, + Package, help_text='The Package that this file is from', null=False, on_delete=models.CASCADE, @@ -115,12 +68,11 @@ class Meta: abstract = True @classmethod - def index(cls, sha1, indexable_package): + def index(cls, sha1, package): try: - package_data = indexable_package.package sha1_bin = hexstring_to_binarray(sha1) bfi, created = cls.objects.get_or_create( - package=indexable_package, + package=package, sha1=sha1_bin ) if created: @@ -128,7 +80,7 @@ def index(cls, sha1, indexable_package): '{} - Inserted {} for Package {}:\t{}'.format( datetime.utcnow().isoformat(), bfi.__class__.__name__, - package_data.download_url, + package.download_url, sha1 ) ) @@ -136,8 +88,8 @@ def index(cls, sha1, indexable_package): except Exception as e: msg = f'Error creating {bfi.__class__.__name__}:\n' msg += get_error_message(e) - indexable_package.index_error = msg - indexable_package.save() + package.index_error = msg + package.save() logger.error(msg) @classmethod @@ -155,8 +107,8 @@ def match(cls, sha1): matches = cls.objects.filter(sha1=sha1_in_bin) if TRACE: for match in matches: - indexable_package = match.package - dct = model_to_dict(indexable_package.package) + package = match.package + dct = model_to_dict(package) logger_debug(cls.__name__, 'match:', 'matched_file:', dct) return matches @@ -228,7 +180,7 @@ class BaseDirectoryIndex(models.Model): ) package = models.ForeignKey( - IndexablePackage, + Package, help_text='The Package that this directory is a part of', null=False, on_delete=models.CASCADE, @@ -247,7 +199,7 @@ def __str__(self): return self.fingerprint() @classmethod - def index(cls, directory_fingerprint, resource_path, indexable_package): + def index(cls, directory_fingerprint, resource_path, package): """ Index the string `directory_fingerprint` into the BaseDirectoryIndex model """ @@ -261,14 +213,14 @@ def index(cls, directory_fingerprint, resource_path, indexable_package): chunk3=fp_chunk3, chunk4=fp_chunk4, path=resource_path, - package=indexable_package, + package=package, ) if created: logger.info( '{} - Inserted {} for Package {}:\t{}'.format( datetime.utcnow().isoformat(), bdi.__class__.__name__, - indexable_package.package.download_url, + package.download_url, directory_fingerprint ) ) @@ -276,8 +228,8 @@ def index(cls, directory_fingerprint, resource_path, indexable_package): except Exception as e: msg = f'Error creating {bdi.__class__.__name__}:\n' msg += get_error_message(e) - indexable_package.index_error = msg - indexable_package.save() + package.index_error = msg + package.save() logger.error(msg) @classmethod @@ -317,7 +269,7 @@ def match(cls, directory_fingerprint): if TRACE: for match in matches: dct = model_to_dict(match) - logger_debug(cls.__name__, 'match:', 'matched_indexable_package:', dct) + logger_debug(cls.__name__, 'match:', 'matched_package:', dct) # Step 2: calculate Hamming distance of all matches @@ -355,7 +307,7 @@ def match(cls, directory_fingerprint): if TRACE: for match in good_matches: dct = model_to_dict(match) - logger_debug(cls.__name__, 'match:', 'good_matched_indexable_package:', dct) + logger_debug(cls.__name__, 'match:', 'good_matched_package:', dct) return good_matches diff --git a/matchcode/src/matchcode/utils.py b/matchcode/src/matchcode/utils.py index 8d3c381a..09a603e8 100644 --- a/matchcode/src/matchcode/utils.py +++ b/matchcode/src/matchcode/utils.py @@ -116,14 +116,12 @@ def index_packages_sha1(): Reindex all the packages for exact sha1 matching. """ from matchcode.models import ExactPackageArchiveIndex - from matchcode.models import get_or_create_indexable_package from packagedb.models import Package for package in Package.objects.filter(sha1__isnull=False): - indexable_package, _ = get_or_create_indexable_package(package) sha1_in_bin = hexstring_to_binarray(package.sha1) _ = ExactPackageArchiveIndex.objects.create( - package=indexable_package, + package=package, sha1=sha1_in_bin ) @@ -133,9 +131,7 @@ def index_package_files_sha1(package, scan_location): Index for SHA1 the package files found in the JSON scan at scan_location """ from matchcode.models import ExactFileIndex - from matchcode.models import get_or_create_indexable_package - indexable_package, _ = get_or_create_indexable_package(package) resource_attributes = dict() vc = VirtualCodebase( location=scan_location, @@ -149,7 +145,7 @@ def index_package_files_sha1(package, scan_location): sha1_in_bin = hexstring_to_binarray(sha1) package_file, created = ExactFileIndex.objects.get_or_create( sha1=sha1_in_bin, - package=indexable_package, + package=package, ) ################# GENERAL UTILITIES ################# diff --git a/matchcode/tests/matchcode/test_api.py b/matchcode/tests/matchcode/test_api.py index 316bd236..20136dce 100644 --- a/matchcode/tests/matchcode/test_api.py +++ b/matchcode/tests/matchcode/test_api.py @@ -14,7 +14,6 @@ from packagedb.models import Package from matchcode.indexing import index_package_directories -from matchcode.models import get_or_create_indexable_package from matchcode.utils import load_resources_from_scan from matchcode.utils import MatchcodeTestCase @@ -37,8 +36,7 @@ def setUp(self): type='npm', ) load_resources_from_scan(self.get_test_loc('match/nested/plugin-request-2.4.1-ip.json'), self.test_package1) - self.test_indexable_package1, _ = get_or_create_indexable_package(self.test_package1) - index_package_directories(self.test_indexable_package1) + index_package_directories(self.test_package1) self.test_package2, _ = Package.objects.get_or_create( filename='underscore-1.10.9.tgz', @@ -50,8 +48,7 @@ def setUp(self): type='npm', ) load_resources_from_scan(self.get_test_loc('match/nested/underscore-1.10.9-ip.json'), self.test_package2) - self.test_indexable_package2, _ = get_or_create_indexable_package(self.test_package2) - index_package_directories(self.test_indexable_package2) + index_package_directories(self.test_package2) def test_api_approximate_directory_content_index_list_fingerprint_lookup(self): test_fingerprint = '00000007af7d63765c78fa516b5353f5ffa7df45' diff --git a/matchcode/tests/matchcode/test_index_packages.py b/matchcode/tests/matchcode/test_index_packages.py index 87528ade..c186466f 100644 --- a/matchcode/tests/matchcode/test_index_packages.py +++ b/matchcode/tests/matchcode/test_index_packages.py @@ -12,7 +12,7 @@ from commoncode.resource import VirtualCodebase from matchcode.fingerprinting import compute_directory_fingerprints -from matchcode.indexing import _create_virtual_codebase_from_indexable_package +from matchcode.indexing import _create_virtual_codebase_from_package_resources from matchcode.indexing import index_directory_fingerprints from matchcode.indexing import index_package_archives from matchcode.indexing import index_package_directories @@ -22,10 +22,8 @@ from matchcode.models import ApproximateDirectoryStructureIndex from matchcode.models import create_halohash_chunks from matchcode.models import hexstring_to_binarray -from matchcode.models import IndexablePackage from matchcode.models import ExactPackageArchiveIndex from matchcode.models import ExactFileIndex -from matchcode.models import get_or_create_indexable_package from matchcode.utils import load_resources_from_scan from matchcode.utils import MatchcodeTestCase from packagedb.models import Package @@ -55,10 +53,9 @@ def setUp(self): load_resources_from_scan(self.scan1, self.test_package1) def test_index_packages(self): - # Ensure ApproximateDirectoryStructureIndex, IndexablePackage, - # ExactPackageArchiveIndex, and ExactFileIndex tables are empty + # Ensure ApproximateDirectoryStructureIndex, ExactPackageArchiveIndex, + # and ExactFileIndex tables are empty self.assertFalse(ApproximateDirectoryStructureIndex.objects.all()) - self.assertFalse(IndexablePackage.objects.all()) self.assertFalse(ExactPackageArchiveIndex.objects.all()) self.assertFalse(ExactFileIndex.objects.all()) @@ -67,19 +64,12 @@ def test_index_packages(self): package_indexer.handle() # See if the tables have been populated properly - indexable_packages = IndexablePackage.objects.all() - self.assertEqual(1, len(indexable_packages)) - indexable_package = indexable_packages[0] - self.assertEqual(self.test_package1.uuid, indexable_package.uuid) - self.assertTrue(indexable_package.last_indexed_date) - self.assertFalse(indexable_package.index_error) - package_archive_sha1s = ExactPackageArchiveIndex.objects.all() self.assertEqual(1, len(package_archive_sha1s)) package_archive_sha1 = package_archive_sha1s[0] expected_sha1 = self.test_package1.sha1 self.assertEqual(expected_sha1, package_archive_sha1.fingerprint()) - self.assertEqual(indexable_package, package_archive_sha1.package) + self.assertEqual(self.test_package1, package_archive_sha1.package) vc = VirtualCodebase(location=self.scan1) expected_resources = [r for r in vc.walk(topdown=True) if r.type == 'file'] @@ -87,16 +77,16 @@ def test_index_packages(self): self.assertEqual(len(expected_resources), len(package_file_sha1s)) for expected_resource, package_file_sha1 in zip(expected_resources, package_file_sha1s): self.assertEqual(expected_resource.sha1, package_file_sha1.fingerprint()) - self.assertEqual(indexable_package, package_file_sha1.package) + self.assertEqual(self.test_package1, package_file_sha1.package) - directory_structure_fingerprints = ApproximateDirectoryStructureIndex.objects.filter(package=indexable_package).order_by('path') + directory_structure_fingerprints = ApproximateDirectoryStructureIndex.objects.filter(package=self.test_package1).order_by('path') # Only one directory should be indexed since we do not create directory # fingerprints for directories with only one file in them self.assertEqual(1, len(directory_structure_fingerprints)) result_1 = directory_structure_fingerprints[0] self.assertEqual('test', result_1.path) - self.assertEqual(indexable_package, result_1.package) + self.assertEqual(self.test_package1, result_1.package) r1_chunk1, r1_chunk2, r1_chunk3, r1_chunk4 = create_halohash_chunks('160440008028c38c24a8038040006040') self.assertEqual(r1_chunk1, result_1.chunk1) self.assertEqual(r1_chunk2, result_1.chunk2) @@ -104,14 +94,13 @@ def test_index_packages(self): self.assertEqual(r1_chunk4, result_1.chunk4) def test_index_packages_index_directory_structure_fingerprints(self): - indexable_package, _ = get_or_create_indexable_package(self.test_package1) - index_packages.index_package_directories(indexable_package) - directory_structure_fingerprints = ApproximateDirectoryStructureIndex.objects.filter(package=indexable_package).order_by('path') + index_packages.index_package_directories(self.test_package1) + directory_structure_fingerprints = ApproximateDirectoryStructureIndex.objects.filter(package=self.test_package1).order_by('path') self.assertEqual(1, len(directory_structure_fingerprints)) result_1 = directory_structure_fingerprints[0] self.assertEqual('test', result_1.path) - self.assertEqual(indexable_package, result_1.package) + self.assertEqual(self.test_package1, result_1.package) expected_chunk1 = hexstring_to_binarray('16044000') expected_chunk2 = hexstring_to_binarray('8028c38c') @@ -127,11 +116,8 @@ def test_index_package_archives(self): # Ensure ExactPackageArchiveIndex table is empty self.assertFalse(ExactPackageArchiveIndex.objects.all()) - # Create indexable_package from test_package - indexable_package, _ = get_or_create_indexable_package(self.test_package1) - # Load ExactPackageArchiveIndex table - created = index_package_archives(indexable_package) + created = index_package_archives(self.test_package1) # Check to see if new ExactPackageArchiveIndex was created self.assertTrue(created) @@ -141,18 +127,15 @@ def test_index_package_archives(self): result = ExactPackageArchiveIndex.objects.all()[0] self.assertEqual(self.test_package1.sha1, result.fingerprint()) - self.assertEqual(indexable_package, result.package) + self.assertEqual(self.test_package1, result.package) def test_index_package_file(self): - # Create indexable_package from test_package - indexable_package, _ = get_or_create_indexable_package(self.test_package1) - # Ensure ExactFileIndex is empty prior to test self.assertFalse(ExactFileIndex.objects.all()) # Get one resource from test_package1 and index it - resource = indexable_package.resources.filter(is_file=True)[0] - created_exact_file_index, _ = index_package_file(resource) + resource = self.test_package1.resources.filter(is_file=True)[0] + created_exact_file_index = index_package_file(resource) self.assertTrue(created_exact_file_index) self.assertEqual(1, ExactFileIndex.objects.all().count()) @@ -160,13 +143,10 @@ def test_index_package_file(self): expected_fingerprint = '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8' self.assertEqual(expected_fingerprint, result.fingerprint()) - self.assertEqual(indexable_package, result.package) + self.assertEqual(self.test_package1, result.package) - def test__create_virtual_codebase_from_indexable_package(self): - # Create indexable_package from test_package - indexable_package, _ = get_or_create_indexable_package(self.test_package1) - - vc = _create_virtual_codebase_from_indexable_package(indexable_package) + def test__create_virtual_codebase_from_package_resources(self): + vc = _create_virtual_codebase_from_package_resources(self.test_package1) expected_vc = VirtualCodebase(location=self.scan1) # Ensure that at least the directory structure is the same @@ -174,16 +154,14 @@ def test__create_virtual_codebase_from_indexable_package(self): self.assertEqual(expected_r.path, r.path) def test_index_directory_fingerprints(self): - # Create indexable_package from test_package - indexable_package, _ = get_or_create_indexable_package(self.test_package1) - vc = _create_virtual_codebase_from_indexable_package(indexable_package) + vc = _create_virtual_codebase_from_package_resources(self.test_package1) vc = compute_directory_fingerprints(vc) # Ensure tables are empty prior to indexing self.assertFalse(ApproximateDirectoryContentIndex.objects.all()) self.assertFalse(ApproximateDirectoryStructureIndex.objects.all()) - indexed_adci, indexed_adsi = index_directory_fingerprints(vc, indexable_package) + indexed_adci, indexed_adsi = index_directory_fingerprints(vc, self.test_package1) # Check to see if anything has been indexed self.assertEqual(1, indexed_adci) @@ -201,14 +179,11 @@ def test_index_directory_fingerprints(self): self.assertEqual(expected_adsi_fingerprint, adsi.fingerprint()) def test_index_package_directories(self): - # Create indexable_package from test_package - indexable_package, _ = get_or_create_indexable_package(self.test_package1) - # Ensure tables are empty prior to indexing self.assertFalse(ApproximateDirectoryContentIndex.objects.all()) self.assertFalse(ApproximateDirectoryStructureIndex.objects.all()) - indexed_adci, indexed_adsi = index_package_directories(indexable_package) + indexed_adci, indexed_adsi = index_package_directories(self.test_package1) # Check to see if anything has been indexed self.assertEqual(1, indexed_adci) diff --git a/matchcode/tests/matchcode/test_match.py b/matchcode/tests/matchcode/test_match.py index 767ec88e..c5057740 100644 --- a/matchcode/tests/matchcode/test_match.py +++ b/matchcode/tests/matchcode/test_match.py @@ -21,7 +21,6 @@ from matchcode.match import EXACT_FILE_MATCH from matchcode.match import do_match from matchcode.match import path_suffixes -from matchcode.models import get_or_create_indexable_package from matchcode.utils import index_package_files_sha1 from matchcode.utils import index_packages_sha1 from matchcode.utils import load_resources_from_scan @@ -97,13 +96,12 @@ def setUp(self): type='maven' ) self.test_package4_metadata = self.test_package4.to_dict() - self.test_indexable_package4, _ = get_or_create_indexable_package(self.test_package4) # Populate ExactPackageArchiveIndexFingerprint table index_packages_sha1() load_resources_from_scan(self.get_test_loc('models/match-test.json'), self.test_package4) - index_package_directories(self.test_indexable_package4) + index_package_directories(self.test_package4) index_package_files_sha1(self.test_package4, self.get_test_loc('models/match-test.json')) def test_do_match_package_archive_match(self): @@ -150,8 +148,7 @@ def setUp(self): type='npm', ) load_resources_from_scan(self.get_test_loc('match/nested/plugin-request-2.4.1-ip.json'), self.test_package1) - self.test_indexable_package1, _ = get_or_create_indexable_package(self.test_package1) - index_package_directories(self.test_indexable_package1) + index_package_directories(self.test_package1) self.test_package2, _ = Package.objects.get_or_create( filename='underscore-1.10.9.tgz', @@ -163,8 +160,7 @@ def setUp(self): type='npm', ) load_resources_from_scan(self.get_test_loc('match/nested/underscore-1.10.9-ip.json'), self.test_package2) - self.test_indexable_package2, _ = get_or_create_indexable_package(self.test_package2) - index_package_directories(self.test_indexable_package2) + index_package_directories(self.test_package2) def test_do_match_approximate_directory_structure_match(self): input_file = self.get_test_loc('match/nested/nested.json') @@ -196,8 +192,7 @@ def setUp(self): download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.0.3.tgz', ) load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.0.3-i.json'), self.test_package1) - self.test_indexable_package1, _ = get_or_create_indexable_package(self.test_package1) - index_package_directories(self.test_indexable_package1) + index_package_directories(self.test_package1) self.test_package2, _ = Package.objects.get_or_create( filename='abbrev-1.0.4.tgz', @@ -208,8 +203,7 @@ def setUp(self): download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.0.4.tgz', ) load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.0.4-i.json'), self.test_package2) - self.test_indexable_package2, _ = get_or_create_indexable_package(self.test_package2) - index_package_directories(self.test_indexable_package2) + index_package_directories(self.test_package2) self.test_package3, _ = Package.objects.get_or_create( filename='abbrev-1.0.5.tgz', @@ -220,8 +214,7 @@ def setUp(self): download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.0.5.tgz', ) load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.0.5-i.json'), self.test_package3) - self.test_indexable_package3, _ = get_or_create_indexable_package(self.test_package3) - index_package_directories(self.test_indexable_package3) + index_package_directories(self.test_package3) self.test_package4, _ = Package.objects.get_or_create( filename='abbrev-1.0.6.tgz', @@ -232,8 +225,7 @@ def setUp(self): download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.0.6.tgz', ) load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.0.6-i.json'), self.test_package4) - self.test_indexable_package4, _ = get_or_create_indexable_package(self.test_package4) - index_package_directories(self.test_indexable_package4) + index_package_directories(self.test_package4) self.test_package5, _ = Package.objects.get_or_create( filename='abbrev-1.0.7.tgz', @@ -244,8 +236,7 @@ def setUp(self): download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz', ) load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.0.7-i.json'), self.test_package5) - self.test_indexable_package5, _ = get_or_create_indexable_package(self.test_package5) - index_package_directories(self.test_indexable_package5) + index_package_directories(self.test_package5) self.test_package6, _ = Package.objects.get_or_create( filename='abbrev-1.0.9.tgz', @@ -256,8 +247,7 @@ def setUp(self): download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz', ) load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.0.9-i.json'), self.test_package6) - self.test_indexable_package6, _ = get_or_create_indexable_package(self.test_package6) - index_package_directories(self.test_indexable_package6) + index_package_directories(self.test_package6) self.test_package7, _ = Package.objects.get_or_create( filename='abbrev-1.1.0.tgz', @@ -268,8 +258,7 @@ def setUp(self): download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz', ) load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.1.0-i.json'), self.test_package7) - self.test_indexable_package7, _ = get_or_create_indexable_package(self.test_package7) - index_package_directories(self.test_indexable_package7) + index_package_directories(self.test_package7) self.test_package8, _ = Package.objects.get_or_create( filename='abbrev-1.1.1.tgz', @@ -280,8 +269,7 @@ def setUp(self): download_url='https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz', ) load_resources_from_scan(self.get_test_loc('match/directory-matching/abbrev-1.1.1-i.json'), self.test_package8) - self.test_indexable_package8, _ = get_or_create_indexable_package(self.test_package8) - index_package_directories(self.test_indexable_package8) + index_package_directories(self.test_package8) def test_match_ApproximateDirectoryStructureIndex_abbrev_1_0_3(self): input_file = self.get_test_loc('match/directory-matching/abbrev-1.0.3-i.json') diff --git a/matchcode/tests/matchcode/test_models.py b/matchcode/tests/matchcode/test_models.py index 561a8ed1..38068c03 100644 --- a/matchcode/tests/matchcode/test_models.py +++ b/matchcode/tests/matchcode/test_models.py @@ -18,7 +18,6 @@ from matchcode.models import ApproximateDirectoryContentIndex from matchcode.models import ApproximateDirectoryStructureIndex from matchcode.models import create_halohash_chunks -from matchcode.models import get_or_create_indexable_package from matchcode.models import hexstring_to_binarray from matchcode.models import ExactPackageArchiveIndex from matchcode.models import ExactFileIndex @@ -86,21 +85,20 @@ def setUp(self): type='maven' ) self.test_package4_metadata = self.test_package4.to_dict() - self.test_indexable_package4, _ = get_or_create_indexable_package(self.test_package4) # Populate ExactPackageArchiveIndexFingerprint table index_packages_sha1() # Populate ExactFileIndexFingerprint table load_resources_from_scan(self.get_test_loc('models/match-test.json'), self.test_package4) - index_package_directories(self.test_indexable_package4) + index_package_directories(self.test_package4) index_package_files_sha1(self.test_package4, self.get_test_loc('models/match-test.json')) class ExactPackageArchiveIndexModelTestCase(BaseModelTest): def test_ExactPackageArchiveIndex_single_sha1_single_match(self): result = ExactPackageArchiveIndex.match('51d28a27d919ce8690a40f4f335b9d591ceb16e9') - result = [r.package.package.to_dict() for r in result] + result = [r.package.to_dict() for r in result] expected = [self.test_package1_metadata] self.assertEqual(expected, result) @@ -122,7 +120,7 @@ def test_ExactFileIndex_match(self): for resource in codebase.walk(topdown=True): matches = ExactFileIndex.match(resource.sha1) for match in matches: - p = match.package.package.to_dict() + p = match.package.to_dict() p['match_type'] = 'exact' codebase.attributes.matches.append(p) resource.matched_to.append(p['purl']) @@ -149,8 +147,7 @@ def setUp(self): ) self.test_package1_metadata = self.test_package1.to_dict() load_resources_from_scan(self.get_test_loc('models/directory-matching/async-0.2.10.tgz-i.json'), self.test_package1) - self.test_indexablepackage1, _ = get_or_create_indexable_package(self.test_package1) - index_package_directories(self.test_indexablepackage1) + index_package_directories(self.test_package1) self.test_package2, _ = Package.objects.get_or_create( filename='async-0.2.9.tgz', @@ -164,8 +161,7 @@ def setUp(self): ) self.test_package2_metadata = self.test_package2.to_dict() load_resources_from_scan(self.get_test_loc('models/directory-matching/async-0.2.9-i.json'), self.test_package2) - self.test_indexablepackage2, _ = get_or_create_indexable_package(self.test_package2) - index_package_directories(self.test_indexablepackage2) + index_package_directories(self.test_package2) def test_ApproximateDirectoryStructureIndex_match_subdir(self): scan_location = self.get_test_loc('models/directory-matching/async-0.2.9-i.json') @@ -182,7 +178,7 @@ def test_ApproximateDirectoryStructureIndex_match_subdir(self): fp = resource.extra_data.get('directory_structure', '') matches = ApproximateDirectoryStructureIndex.match(fp) for match in matches: - p = match.package.package.to_dict() + p = match.package.to_dict() p['match_type'] = 'approximate-directory-structure' resource.packages.append(p) resource.save(codebase) @@ -205,7 +201,7 @@ def test_ApproximateDirectoryContentIndex_match_subdir(self): fp = resource.extra_data.get('directory_content', '') matches = ApproximateDirectoryContentIndex.match(fp) for match in matches: - p = match.package.package.to_dict() + p = match.package.to_dict() p['match_type'] = 'approximate-directory-content' resource.packages.append(p) resource.save(codebase) diff --git a/minecode/src/clearindex/harvest.py b/minecode/src/clearindex/harvest.py index ef56f6b3..47aa2dce 100644 --- a/minecode/src/clearindex/harvest.py +++ b/minecode/src/clearindex/harvest.py @@ -203,7 +203,8 @@ def map_scancode_harvest(cditem): # download_url value. download_url = package_scan.get('download_url') if not download_url: - err_msg = 'CDitemError: empty download_url for package_scan: {}'.format(package_scan) + purl = package_scan.get('purl') + err_msg = 'CDitemError: empty download_url for package: {}'.format(purl) logger.error(err_msg) cditem.map_error = err_msg diff --git a/minecode/src/clearindex/management/commands/run_clearindex.py b/minecode/src/clearindex/management/commands/run_clearindex.py index 9455d83c..5e13b33f 100644 --- a/minecode/src/clearindex/management/commands/run_clearindex.py +++ b/minecode/src/clearindex/management/commands/run_clearindex.py @@ -269,26 +269,26 @@ def get_or_create_package_from_cditem_definition(cditem): else: # TODO: This is temporary until we fold clearindex into minecode mapping # proper, otherwise we should base this decision off of mining level - if existing_package.mining_level < definition_mining_level: - new_package_data = ScannedPackage( - type=converted_package_type, - namespace=namespace, - name=name, - version=version, - download_url=download_url, - homepage_url=homepage_url, - sha1=sha1, - sha256=sha256, - release_date=release_date, - declared_license=declared_license, - license_expression=normalized_license_expression, - copyright=copyrights, - ).to_dict() - merge_packages( - existing_package=existing_package, - new_package_data=new_package_data, - replace=True - ) + # if existing_package.mining_level < definition_mining_level: + new_package_data = ScannedPackage( + type=converted_package_type, + namespace=namespace, + name=name, + version=version, + download_url=download_url, + homepage_url=homepage_url, + sha1=sha1, + sha256=sha256, + release_date=release_date, + declared_license=declared_license, + license_expression=normalized_license_expression, + copyright=copyrights, + ).to_dict() + merge_packages( + existing_package=existing_package, + new_package_data=new_package_data, + replace=True + ) package = existing_package package.append_to_history('Updated package from CDitem definition: {}'.format(cditem.path)) diff --git a/minecode/src/discovery/management/commands/run_map.py b/minecode/src/discovery/management/commands/run_map.py index b0009fe4..0c2397af 100644 --- a/minecode/src/discovery/management/commands/run_map.py +++ b/minecode/src/discovery/management/commands/run_map.py @@ -39,7 +39,7 @@ from discovery.utils import stringify_null_purl_fields -TRACE = False +TRACE = True logger = logging.getLogger(__name__) logging.basicConfig(stream=sys.stdout) diff --git a/packagedb/src/packagedb/migrations/0052_package_index_error_package_last_indexed_date.py b/packagedb/src/packagedb/migrations/0052_package_index_error_package_last_indexed_date.py new file mode 100644 index 00000000..bafd588e --- /dev/null +++ b/packagedb/src/packagedb/migrations/0052_package_index_error_package_last_indexed_date.py @@ -0,0 +1,31 @@ +# Generated by Django 4.1.2 on 2022-12-08 01:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("packagedb", "0051_package_api_data_url_package_datasource_id_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="package", + name="index_error", + field=models.TextField( + blank=True, + help_text="Indexing errors messages. When present this means the indexing has failed.", + null=True, + ), + ), + migrations.AddField( + model_name="package", + name="last_indexed_date", + field=models.DateTimeField( + blank=True, + help_text="Timestamp set to the date of the last indexing. Used to track indexing status.", + null=True, + ), + ), + ] diff --git a/packagedb/src/packagedb/models.py b/packagedb/src/packagedb/models.py index aacf2859..8f31923e 100644 --- a/packagedb/src/packagedb/models.py +++ b/packagedb/src/packagedb/models.py @@ -427,7 +427,16 @@ class Package( help_text=_('A list of source package URLs (aka. "purl") for this package. ' 'For instance an SRPM is the "source package" for a binary RPM.'), ) - + last_indexed_date = models.DateTimeField( + null=True, + blank=True, + help_text='Timestamp set to the date of the last indexing. Used to track indexing status.' + ) + index_error = models.TextField( + null=True, + blank=True, + help_text='Indexing errors messages. When present this means the indexing has failed.', + ) search_vector = SearchVectorField(null=True) objects = PackageQuerySet.as_manager() From bcca268f7b74f2d5d5b2d59715a303f0730ea2dc Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Thu, 8 Dec 2022 20:48:48 +0000 Subject: [PATCH 15/38] Update match API to return URL to Package * Update expected test results Signed-off-by: Jono Yang --- matchcode/src/matchcode/api.py | 23 ++++++++++++++--------- matchcode/tests/matchcode/test_api.py | 22 ++++++++++++---------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/matchcode/src/matchcode/api.py b/matchcode/src/matchcode/api.py index 3a27e9b1..042d8e9e 100644 --- a/matchcode/src/matchcode/api.py +++ b/matchcode/src/matchcode/api.py @@ -15,6 +15,7 @@ from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.serializers import CharField +from rest_framework.serializers import HyperlinkedRelatedField from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ReadOnlyField from rest_framework.serializers import Serializer @@ -31,7 +32,7 @@ class BaseFileIndexSerializer(ModelSerializer): sha1 = CharField(source='fingerprint') - purl = CharField(source='package.package_url') + package = HyperlinkedRelatedField(view_name='api:package-detail', lookup_field='uuid', read_only=True) class ExactFileIndexSerializer(BaseFileIndexSerializer): @@ -39,7 +40,7 @@ class Meta: model = ExactFileIndex fields = ( 'sha1', - 'purl' + 'package' ) @@ -48,13 +49,13 @@ class Meta: model = ExactPackageArchiveIndex fields = ( 'sha1', - 'purl' + 'package' ) class BaseDirectoryIndexSerializer(ModelSerializer): fingerprint = ReadOnlyField() - purl = CharField(source='package.package_url') + package = HyperlinkedRelatedField(view_name='api:package-detail', lookup_field='uuid', read_only=True) class ApproximateDirectoryContentIndexSerializer(BaseDirectoryIndexSerializer): @@ -62,7 +63,7 @@ class Meta: model = ApproximateDirectoryContentIndex fields = ( 'fingerprint', - 'purl', + 'package', ) @@ -71,14 +72,14 @@ class Meta: model = ApproximateDirectoryStructureIndex fields = ( 'fingerprint', - 'purl', + 'package', ) class BaseDirectoryIndexMatchSerializer(Serializer): fingerprint = CharField() matched_fingerprint = CharField() - purl = CharField() + package = HyperlinkedRelatedField(view_name='api:package-detail', lookup_field='uuid', read_only=True) class CharMultipleWidget(widgets.TextInput): @@ -241,11 +242,15 @@ def match(self, request): { 'fingerprint': fingerprint, 'matched_fingerprint': match.fingerprint(), - 'purl': match.package.package_url, + 'package': match.package, } ) - serialized_match_results = BaseDirectoryIndexMatchSerializer(results, many=True) + serialized_match_results = BaseDirectoryIndexMatchSerializer( + results, + context={'request': request}, + many=True + ) return Response(serialized_match_results.data) diff --git a/matchcode/tests/matchcode/test_api.py b/matchcode/tests/matchcode/test_api.py index 20136dce..be971081 100644 --- a/matchcode/tests/matchcode/test_api.py +++ b/matchcode/tests/matchcode/test_api.py @@ -60,9 +60,10 @@ def test_api_approximate_directory_content_index_list_fingerprint_lookup(self): results = response.data.get('results', []) self.assertEqual(1, len(results)) result = results[0] + expected_package = 'http://testserver' + reverse('api:package-detail', args=[self.test_package1.uuid]) expected_result = { 'fingerprint': '00000007af7d63765c78fa516b5353f5ffa7df45', - 'purl': 'pkg:npm/plugin-request@2.4.1' + 'package': expected_package } self.assertEqual(expected_result, result) @@ -76,9 +77,10 @@ def test_api_approximate_directory_structure_index_list_fingerprint_lookup(self) results = response.data.get('results', []) self.assertEqual(1, len(results)) result = results[0] + expected_package = 'http://testserver' + reverse('api:package-detail', args=[self.test_package2.uuid]) expected_result = { 'fingerprint': '00000004d10982208810240820080a6a3e852486', - 'purl': 'pkg:npm/underscore@1.10.9' + 'package': expected_package } self.assertEqual(expected_result, result) @@ -113,8 +115,8 @@ def test_api_approximate_directory_content_index_match_close_match(self): self.assertEqual(test_fingerprint, result['fingerprint']) expected_matched_fingerprint = '00000007af7d63765c78fa516b5353f5ffa7df45' self.assertEqual(expected_matched_fingerprint, result['matched_fingerprint']) - expected_purl = 'pkg:npm/plugin-request@2.4.1' - self.assertEqual(expected_purl, result['purl']) + expected_package = 'http://testserver' + reverse('api:package-detail', args=[self.test_package1.uuid]) + self.assertEqual(expected_package, result['package']) def test_api_approximate_directory_structure_index_match_close_match(self): # This test fingerprint has a hamming distance of 7 from the expected fingerprint @@ -129,8 +131,8 @@ def test_api_approximate_directory_structure_index_match_close_match(self): self.assertEqual(test_fingerprint, result['fingerprint']) expected_matched_fingerprint = '00000004d10982208810240820080a6a3e852486' self.assertEqual(expected_matched_fingerprint, result['matched_fingerprint']) - expected_purl = 'pkg:npm/underscore@1.10.9' - self.assertEqual(expected_purl, result['purl']) + expected_package = 'http://testserver' + reverse('api:package-detail', args=[self.test_package2.uuid]) + self.assertEqual(expected_package, result['package']) def test_api_approximate_directory_content_index_match(self): test_fingerprint = '00000007af7d63765c78fa516b5353f5ffa7df45' @@ -143,8 +145,8 @@ def test_api_approximate_directory_content_index_match(self): result = results[0] self.assertEqual(test_fingerprint, result['fingerprint']) self.assertEqual(test_fingerprint, result['matched_fingerprint']) - expected_purl = 'pkg:npm/plugin-request@2.4.1' - self.assertEqual(expected_purl, result['purl']) + expected_package = 'http://testserver' + reverse('api:package-detail', args=[self.test_package1.uuid]) + self.assertEqual(expected_package, result['package']) def test_api_approximate_directory_structure_index_match(self): test_fingerprint = '00000004d10982208810240820080a6a3e852486' @@ -157,5 +159,5 @@ def test_api_approximate_directory_structure_index_match(self): result = results[0] self.assertEqual(test_fingerprint, result['fingerprint']) self.assertEqual(test_fingerprint, result['matched_fingerprint']) - expected_purl = 'pkg:npm/underscore@1.10.9' - self.assertEqual(expected_purl, result['purl']) + expected_package = 'http://testserver' + reverse('api:package-detail', args=[self.test_package2.uuid]) + self.assertEqual(expected_package, result['package']) From 0ea31ea6b5e883e976c47185c00c53101cf2dfd4 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Thu, 8 Dec 2022 23:54:04 +0000 Subject: [PATCH 16/38] Update matching plugin #13 Signed-off-by: Jono Yang --- matchcode/src/matchcode/api.py | 19 +++++-- matchcode/src/matchcode/plugin_match.py | 74 ++++++++++++------------- 2 files changed, 49 insertions(+), 44 deletions(-) diff --git a/matchcode/src/matchcode/api.py b/matchcode/src/matchcode/api.py index 042d8e9e..3cf13a2e 100644 --- a/matchcode/src/matchcode/api.py +++ b/matchcode/src/matchcode/api.py @@ -32,7 +32,11 @@ class BaseFileIndexSerializer(ModelSerializer): sha1 = CharField(source='fingerprint') - package = HyperlinkedRelatedField(view_name='api:package-detail', lookup_field='uuid', read_only=True) + package = HyperlinkedRelatedField( + view_name='api:package-detail', + lookup_field='uuid', + read_only=True + ) class ExactFileIndexSerializer(BaseFileIndexSerializer): @@ -55,8 +59,11 @@ class Meta: class BaseDirectoryIndexSerializer(ModelSerializer): fingerprint = ReadOnlyField() - package = HyperlinkedRelatedField(view_name='api:package-detail', lookup_field='uuid', read_only=True) - + package = HyperlinkedRelatedField( + view_name='api:package-detail', + lookup_field='uuid', + read_only=True + ) class ApproximateDirectoryContentIndexSerializer(BaseDirectoryIndexSerializer): class Meta: @@ -79,7 +86,11 @@ class Meta: class BaseDirectoryIndexMatchSerializer(Serializer): fingerprint = CharField() matched_fingerprint = CharField() - package = HyperlinkedRelatedField(view_name='api:package-detail', lookup_field='uuid', read_only=True) + package = HyperlinkedRelatedField( + view_name='api:package-detail', + lookup_field='uuid', + read_only=True + ) class CharMultipleWidget(widgets.TextInput): diff --git a/matchcode/src/matchcode/plugin_match.py b/matchcode/src/matchcode/plugin_match.py index 23b63069..ab7d8697 100644 --- a/matchcode/src/matchcode/plugin_match.py +++ b/matchcode/src/matchcode/plugin_match.py @@ -26,26 +26,21 @@ class PackageInfo: - def __init__(self, purl): - self.purl = purl - self.package_resources = self.get_resources_from_packagedb(purl) + def __init__(self, packagedb_url): + self.packagedb_url = packagedb_url + self.package_resources = self.get_resources_from_packagedb(packagedb_url) self.package_resource_by_paths = self.create_package_resource_by_paths() @classmethod - def get_resources_from_packagedb(cls, purl): + def get_resources_from_packagedb(cls, packagedb_url): # Get package resources - payload = { - 'purl': purl - } package_resources = [] - response = requests.get(PACKAGEDB_RESOURCES_ENDPOINT, params=payload) - while response: - jsoned_response = response.json() - package_resources.extend(jsoned_response.get('results', [])) - next_page = jsoned_response.get('next') - if not next_page: - break - response = requests.get(next_page) + response = requests.get(packagedb_url) + if response: + package_data = response.json() + resources_url = package_data.get('resources') + response = requests.get(resources_url) + package_resources.extend(response.json()) return package_resources def create_package_resource_by_paths(self): @@ -74,16 +69,16 @@ def check_resource_path(resource, package_resources_by_path): return False -def determine_best_package_match(directory, codebase, package_info_by_purl): +def determine_best_package_match(directory, codebase, package_info_by_packagedb_url): """ For all potential package matches in `package_info_by_purl`, return the package whose codebase structure matches ours the most. """ # Calculate the percent of package files found in codebase - purls_by_match_ratio = {} - matched_codebase_paths_by_purl = defaultdict(list) - for matched_package_purl, package_info in package_info_by_purl.items(): - matched_codebase_paths = matched_codebase_paths_by_purl[matched_package_purl] + packgedb_urls_by_match_ratio = {} + matched_codebase_paths_by_packagedb_url = defaultdict(list) + for matched_packagedb_url, package_info in package_info_by_packagedb_url.items(): + matched_codebase_paths = matched_codebase_paths_by_packagedb_url[matched_packagedb_url] package_resource_by_paths = package_info.package_resource_by_paths # TODO: Theres a problem when try to match the directory with @@ -101,11 +96,11 @@ def determine_best_package_match(directory, codebase, package_info_by_purl): matching_resources_count = len(matched_codebase_paths) ratio = matching_resources_count / len(package_resource_by_paths) - purls_by_match_ratio[ratio] = matched_package_purl + packgedb_urls_by_match_ratio[ratio] = matched_packagedb_url - highest_match_ratio = max(match_ratio for match_ratio, _ in purls_by_match_ratio.items()) - best_package_match_purl = purls_by_match_ratio[highest_match_ratio] - return best_package_match_purl, matched_codebase_paths_by_purl[best_package_match_purl] + highest_match_ratio = max(match_ratio for match_ratio, _ in packgedb_urls_by_match_ratio.items()) + best_package_match_packagedb_url = packgedb_urls_by_match_ratio[highest_match_ratio] + return best_package_match_packagedb_url, matched_codebase_paths_by_packagedb_url[best_package_match_packagedb_url] @post_scan_impl @@ -159,31 +154,30 @@ def process_codebase(self, codebase, **kwargs): response = requests.get(MATCHCODE_ENDPOINT, params=payload) if response: results = response.json() - matched_purls = [result.get('purl', '') for result in results] - if not matched_purls: + matched_packagedb_urls = [result.get('package', '') for result in results] + if not matched_packagedb_urls: continue # Get the paths of the resources from matched packages - package_info_by_purl = {} - for purl in matched_purls: - package_info_by_purl[purl] = PackageInfo(purl) + package_info_by_packagedb_url = {} + for packagedb_url in matched_packagedb_urls: + package_info_by_packagedb_url[packagedb_url] = PackageInfo(packagedb_url) # Calculate the percent of package files found in codebase - best_package_match_purl, matched_codebase_paths = determine_best_package_match(resource, codebase, package_info_by_purl) + best_package_match_packagedb_url, matched_codebase_paths = determine_best_package_match( + resource, + codebase, + package_info_by_packagedb_url + ) # Query PackageDB for info on the best matched package - payload = { - 'purl': best_package_match_purl - } - response = requests.get(PACKAGEDB_ENDPOINT, params=payload) + response = requests.get(best_package_match_packagedb_url) if response: # Create DiscoveredPackage for the best matched package - packagedb_results = response.json().get('results', []) - for package in packagedb_results: - package.pop('uuid') - if package in codebase.attributes.matches: - continue - codebase.attributes.matches.append(package) + package_data = response.json() + if package_data not in codebase.attributes.matches: + codebase.attributes.matches.append(package_data) + best_package_match_purl = package_data['purl'] # Associate the package to the resource and its children for matched_codebase_path in matched_codebase_paths: From ab715de6b93ba00c0e6dfdd62644bbfeb0325a4f Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Tue, 13 Dec 2022 22:09:05 +0000 Subject: [PATCH 17/38] Separate codebase matching logic into function #13 Signed-off-by: Jono Yang --- matchcode/src/matchcode/plugin_match.py | 129 +++++++++++++----------- 1 file changed, 72 insertions(+), 57 deletions(-) diff --git a/matchcode/src/matchcode/plugin_match.py b/matchcode/src/matchcode/plugin_match.py index ab7d8697..2372c2e1 100644 --- a/matchcode/src/matchcode/plugin_match.py +++ b/matchcode/src/matchcode/plugin_match.py @@ -8,8 +8,9 @@ # from collections import defaultdict -import attr +import os +import attr import requests from commoncode.cliutils import PluggableCommandLineOption @@ -19,10 +20,8 @@ from plugincode.post_scan import post_scan_impl from plugincode.post_scan import PostScanPlugin - -MATCHCODE_ENDPOINT = 'http://127.0.0.1:8001/api/approximate_directory_content_index/match/' -PACKAGEDB_ENDPOINT = 'http://127.0.0.1:8001/api/packages/' -PACKAGEDB_RESOURCES_ENDPOINT = 'http://127.0.0.1:8001/api/resources/' +MATCHCODE_DIRECTORY_CONTENT_MATCHING_ENDPOINT = os.environ.get('MATCHCODE_DIRECTORY_CONTENT_MATCHING_ENDPOINT') +MATCHCODE_DIRECTORY_STRUCTURE_MATCHING_ENDPOINT = os.environ.get('MATCHCODE_DIRECTORY_STRUCTURE_MATCHING_ENDPOINT') class PackageInfo: @@ -40,6 +39,7 @@ def get_resources_from_packagedb(cls, packagedb_url): package_data = response.json() resources_url = package_data.get('resources') response = requests.get(resources_url) + print(response.json()) package_resources.extend(response.json()) return package_resources @@ -103,6 +103,63 @@ def determine_best_package_match(directory, codebase, package_info_by_packagedb_ return best_package_match_packagedb_url, matched_codebase_paths_by_packagedb_url[best_package_match_packagedb_url] +def do_directory_matching(codebase, fingerprint_key, matching_endpoint): + for resource in codebase.walk(topdown=True): + # Collect directory fingerprints, if available + directory_fingerprint = resource.extra_data.get(fingerprint_key, '') + + # Skip resource if it is not a directory, does not contain directory + # fingerprints, or if it has already been matched + if (resource.is_file + or not directory_fingerprint + or resource.extra_data.get('matched', False)): + continue + + # Send fingerprint to matchcode for matching and get the purls of + # the matched packages + payload = { + 'fingerprint': [directory_fingerprint] + } + response = requests.get(matching_endpoint, params=payload) + if response: + results = response.json() + matched_packagedb_urls = [result.get('package', '') for result in results] + if not matched_packagedb_urls: + continue + + # Get the paths of the resources from matched packages + package_info_by_packagedb_url = {} + for packagedb_url in matched_packagedb_urls: + package_info_by_packagedb_url[packagedb_url] = PackageInfo(packagedb_url) + + # Calculate the percent of package files found in codebase + best_package_match_packagedb_url, matched_codebase_paths = determine_best_package_match( + resource, + codebase, + package_info_by_packagedb_url + ) + + # Query PackageDB for info on the best matched package + response = requests.get(best_package_match_packagedb_url) + if response: + # Create DiscoveredPackage for the best matched package + package_data = response.json() + if package_data not in codebase.attributes.matches: + codebase.attributes.matches.append(package_data) + best_package_match_purl = package_data['purl'] + + # Associate the package to the resource and its children + for matched_codebase_path in matched_codebase_paths: + #print('matched_codebase_path: ' + matched_codebase_path) + r = codebase.get_resource(matched_codebase_path) + if best_package_match_purl in r.matched_to: + continue + r.matched_to.append(best_package_match_purl) + r.extra_data['matched'] = True + r.save(codebase) + return codebase + + @post_scan_impl class Match(PostScanPlugin): codebase_attributes = dict( @@ -135,55 +192,13 @@ def is_enabled(self, match, **kwargs): def process_codebase(self, codebase, **kwargs): codebase = compute_directory_fingerprints(codebase) - for resource in codebase.walk(topdown=True): - # Collect directory fingerprints, if available - directory_content_fingerprint = resource.extra_data.get('directory_content', '') - - # Skip resource if it is not a directory, does not contain directory - # fingerprints, or if it has already been matched - if (resource.is_file - or not directory_content_fingerprint - or resource.extra_data.get('matched', False)): - continue - - # Send fingerprint to matchcode for matching and get the purls of - # the matched packages - payload = { - 'fingerprint': [directory_content_fingerprint] - } - response = requests.get(MATCHCODE_ENDPOINT, params=payload) - if response: - results = response.json() - matched_packagedb_urls = [result.get('package', '') for result in results] - if not matched_packagedb_urls: - continue - - # Get the paths of the resources from matched packages - package_info_by_packagedb_url = {} - for packagedb_url in matched_packagedb_urls: - package_info_by_packagedb_url[packagedb_url] = PackageInfo(packagedb_url) - - # Calculate the percent of package files found in codebase - best_package_match_packagedb_url, matched_codebase_paths = determine_best_package_match( - resource, - codebase, - package_info_by_packagedb_url - ) - - # Query PackageDB for info on the best matched package - response = requests.get(best_package_match_packagedb_url) - if response: - # Create DiscoveredPackage for the best matched package - package_data = response.json() - if package_data not in codebase.attributes.matches: - codebase.attributes.matches.append(package_data) - best_package_match_purl = package_data['purl'] - - # Associate the package to the resource and its children - for matched_codebase_path in matched_codebase_paths: - r = codebase.get_resource(matched_codebase_path) - if best_package_match_purl in r.matched_to: - continue - r.matched_to.append(best_package_match_purl) - r.extra_data['matched'] = True - r.save(codebase) + codebase = do_directory_matching( + codebase, + 'directory_content', + MATCHCODE_DIRECTORY_CONTENT_MATCHING_ENDPOINT + ) + codebase = do_directory_matching( + codebase, + 'directory_structure', + MATCHCODE_DIRECTORY_STRUCTURE_MATCHING_ENDPOINT + ) From 5caa11cbf0b8129129ea675fd3e57756dffbc053 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Wed, 14 Dec 2022 01:59:59 +0000 Subject: [PATCH 18/38] Combine codebases into single project Signed-off-by: Jono Yang --- matchcode/.gitattributes => .gitattributes | 0 matchcode/MANIFEST.in => MANIFEST.in | 0 matchcode/Makefile => Makefile | 2 +- apache-2.0.LICENSE | 25 + .../src/matchcode => clearindex}/__init__.py | 0 .../src/clearindex => clearindex}/harvest.py | 0 .../management/__init__.py | 0 .../management/commands}/__init__.py | 0 .../management/commands/run_clearindex.py | 0 .../src/clearindex => clearindex}/utils.py | 0 matchcode/configure => configure | 6 +- {minecode => discovery}/AUTHORS.rst | 0 {minecode => discovery}/CHANGELOG.rst | 0 {minecode => discovery}/NOTICE | 0 {minecode => discovery}/README.rst | 0 .../src/discovery => discovery}/__init__.py | 0 {minecode/src/discovery => discovery}/api.py | 0 {minecode/src/discovery => discovery}/apps.py | 0 .../discovery => discovery}/bsd-new.LICENSE | 0 .../src/discovery => discovery}/command.py | 0 .../src/discovery => discovery}/debutils.py | 0 {minecode/src/discovery => discovery}/ls.py | 0 .../management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/check_licenses.py | 0 .../management/commands/check_uri.py | 0 .../management/commands/dump_purls.py | 0 .../management/commands/get_status.py | 0 .../management/commands/remap.py | 0 .../management/commands/run_map.py | 0 .../management/commands/run_visit.py | 0 .../management/commands/seed.py | 0 .../mappers/__init__.py | 0 .../discovery => discovery}/mappers/debian.py | 0 .../discovery => discovery}/mappers/fdroid.py | 0 .../mappers/freebsd.py | 0 .../discovery => discovery}/mappers/maven.py | 0 .../discovery => discovery}/mappers/npm.py | 0 .../discovery => discovery}/mappers/pypi.py | 0 .../mappers/rubygems.py | 0 .../mappers/sourceforge.py | 0 .../mappings/__init__.py | 0 .../mappings/pypi_trove.py | 0 .../migrations/0001_initial.py | 0 .../migrations/0002_auto_20160707_1249.py | 0 .../migrations/0003_auto_20160712_1223.py | 0 .../migrations/0004_auto_20160713_1731.py | 0 .../migrations/0005_resourceuri_metadata.py | 0 .../0006_remove_resourceuri_metadata.py | 0 ...hange_unique_constraints_on_resourceuri.py | 0 ...hange_default_sort_order_on_resourceuri.py | 0 .../migrations/0009_resourceuri_source_uri.py | 0 .../0010_resourceuri_mining_level.py | 0 .../migrations/0011_auto_20170807_0253.py | 0 .../migrations/0012_auto_20170807_0444.py | 0 .../migrations/0013_auto_20170807_0511.py | 0 .../migrations/0014_auto_20170807_0529.py | 0 .../migrations/0015_auto_20180607_0851.py | 0 .../migrations/0016_remove_resourceuri_sig.py | 0 .../migrations/0017_auto_20180619_2236.py | 0 .../0018_scannableuri_package_id.py | 0 .../migrations/0019_auto_20180716_1648.py | 0 .../0020_resourceuri_package_url.py | 0 .../migrations/0021_auto_20181026_1635.py | 0 .../migrations/0022_auto_20190307_2332.py | 0 .../migrations}/__init__.py | 0 .../src/discovery => discovery}/models.py | 0 .../src/discovery => discovery}/route.py | 0 .../src/discovery => discovery}/rsync.py | 0 .../src/discovery => discovery}/saneyaml.py | 0 {minecode/src/discovery => discovery}/seed.py | 0 .../tests}/__init__.py | 0 .../tests}/test_command.py | 0 .../tests}/test_debian.py | 0 .../tests}/test_fdroid.py | 0 .../tests}/test_freebsd.py | 0 .../tests}/test_housekeeping.py | 0 .../discovery => discovery/tests}/test_ls.py | 0 .../tests}/test_maven.py | 0 .../tests}/test_models.py | 0 .../discovery => discovery/tests}/test_npm.py | 0 .../tests}/test_pypi.py | 0 .../tests}/test_route.py | 0 .../tests}/test_rsync.py | 0 .../tests}/test_rubygems.py | 0 .../tests}/test_run_map.py | 0 .../tests}/test_run_visit.py | 0 .../tests}/test_seed.py | 0 .../tests}/test_sourceforge.py | 0 .../tests}/test_utils.py | 0 .../tests}/test_version.py | 0 .../tests}/testfiles/command/bar | 0 .../tests}/testfiles/command/foo/.gitignore | 0 .../debian/copyright/basic_copyright | 0 ...pyright-abiword_common_copyright.copyright | 0 .../debian/copyright/invalid_copyright | 0 .../testfiles/debian/debian_lslrs_expected | 0 .../debian/debian_lslrs_on_ubuntu_expected | 0 .../debian/debian_sourceindex_expected | 0 .../debian/debutils/3dldf_2.0.3+dfsg-2.dsc | 0 .../debutils/3dldf_2.0.3+dfsg-2.dsc-expected | 0 .../testfiles/debian/debutils/control_basic | 0 .../testfiles/debian/debutils/control_invalid | 0 .../testfiles/debian/dsc/7kaa_2.14.3-1.dsc | 0 .../debian/dsc/description-expected.json | 0 .../testfiles/debian/dsc/description.json | 0 .../debian/dsc/description_expected.json | 0 .../tests}/testfiles/debian/dsc/invalid.dsc | 0 .../testfiles/debian/invalid_files/ls-lR.gz | Bin .../testfiles/debian/lslr/ls-lR_debian.gz | Bin .../debian/lslr/ls-lR_debian.gz-expected.json | 0 .../testfiles/debian/lslr/ls-lR_ubuntu.gz | Bin .../debian/lslr/ls-lR_ubuntu.gz-expected.json | 0 .../testfiles/debian/packages/debian_Packages | 0 .../packages/debian_Packages-expected.json | 0 .../debian_Packages-visit-expected.json | 0 .../testfiles/debian/packages/ubuntu_Packages | 0 .../packages/ubuntu_Packages-expected.json | 0 .../tests}/testfiles/debian/release/Release | 0 .../testfiles/debian/release/Release_expected | 0 .../testfiles/debian/release/Release_with_md5 | 0 .../debian/release/Release_with_md5_expected | 0 .../testfiles/debian/release/visited_Release | 0 .../release/visited_Release-expected.json | 0 .../testfiles/debian/sources/Sources.gz | Bin .../debian/sources/Sources.gz-expected.json | 0 .../testfiles/debian/sources/debian_Sources | 0 ...bian_Sources_mapped-expected-packages.json | 0 .../sources/debian_Sources_visit_expected | 0 .../testfiles/debian/sources/ubuntu_Sources | 0 .../sources/ubuntu_Sources_visit_expected | 0 .../testfiles/debian/status/simple_status | 0 .../tests}/testfiles/directories/find-ls | 0 .../directories/find-ls-apache-start | 0 .../find-ls-apache-start-expected.json | 0 .../directories/find-ls-expected.json | 0 .../tests}/testfiles/directories/ls-lr | 0 .../testfiles/directories/ls-lr-expected.json | 0 .../tests}/testfiles/directories/ls-lr-ubuntu | 0 .../directories/ls-lr-ubuntu-expected.json | 0 .../fdroid/index-v2-expected-visit.json | 0 .../index-v2-visited-expected-mapped.json | 0 .../testfiles/fdroid/index-v2-visited.json | 0 .../tests}/testfiles/fdroid/index-v2.json | 0 .../freebsd/FreeBSD-10-i386_release_0_.html | 0 .../FreeBSD-10-i386_release_0_.html_expected | 0 .../tests}/testfiles/freebsd/FreeBSD.org.html | 0 .../freebsd/FreeBSD.org.html_expected | 0 .../testfiles/freebsd/indexfile_expected | 0 .../freebsd/indexfile_expected_mapper.json | 0 .../tests}/testfiles/freebsd/mapper_input1 | 0 .../tests}/testfiles/freebsd/packagesite.txz | Bin .../tests}/testfiles/freebsd/pkg-devel_index | 0 .../freebsd/pkg-devel_index_mapper.json | 0 .../freebsd/squirrelmail-plugins-1.0_2.txz | Bin .../bytejta-supports-0.5.0-ALPHA4.pom | 0 ...orts-0.5.0-ALPHA4.pom_search_expected.json | 0 .../declared_license_search_expected.json | 0 .../housekeeping/example_expected.json | 0 .../ignore_upper_case_search_expected.json | 0 .../license_expression_search_expected.json | 0 .../end2end/bytejta-supports-0.5.0-ALPHA4.pom | 0 .../end2end/expected_mapped_packages.json | 0 .../maven/end2end/expected_visited_uris.json | 0 .../testfiles/maven/end2end/test_uris.json | 0 .../expected_visited_increment_index.json | 0 .../end2end_index/expected_visited_index.json | 0 .../nexus-maven-repository-index.163.gz | Bin .../nexus-maven-repository-index.properties | 0 .../commons-jaxrs-1.21-index-data.json | 0 .../commons-jaxrs-1.21-pom-data.json | 0 .../end2end_multisteps/commons-jaxrs-1.21.pom | 0 ..._mapped_commons-jaxrs-1.21-from-index.json | 0 ...ed_mapped_commons-jaxrs-1.21-from-pom.json | 0 .../end2end_unicode/commons-jaxrs-1.22.pom | 0 .../expected_mapped_commons-jaxrs-1.22.json | 0 .../expected_visited_commons-jaxrs-1.22.json | 0 .../tests}/testfiles/maven/html/app.html | 0 .../maven/html/jcenter.bintray.com.html | 0 .../maven/html/stateframework-compiler.html | 0 .../maven/html/visitor_expected_app.html.json | 0 ...or_expected_jcenter.bintray.com2.html.json | 0 ...expected_stateframework-compiler.html.json | 0 .../buggy/expected_artifacts-defaults.json | 0 .../maven/index/buggy/expected_artifacts.json | 0 .../maven/index/buggy/expected_entries.json | 0 .../maven/index/buggy/expected_uris.json | 0 .../index/buggy/expected_visited_uris.json | 0 .../buggy/nexus-maven-repository-index.gz | Bin .../index/expected_artifacts-all-worthy.json | 0 .../index/expected_artifacts-defaults.json | 0 .../maven/index/expected_artifacts.json | 0 .../maven/index/expected_entries.json | 0 .../testfiles/maven/index/expected_uris.json | 0 .../expected_artifacts-defaults.json | 0 .../index/increment/expected_artifacts.json | 0 .../index/increment/expected_entries.json | 0 .../increment/expected_properties_uris.json | 0 .../maven/index/increment/expected_uris.json | 0 .../nexus-maven-repository-index.445.gz | Bin .../nexus-maven-repository-index.properties | 0 .../increment2/expected_mini_package.json | 0 .../maven/index/increment2/expected_uris.json | 0 .../nexus-maven-repository-index.457.gz | Bin .../index/nexus-maven-repository-index.gz | Bin .../testfiles/maven/mapper/ant-1.6.5.pom | 0 .../testfiles/maven/mapper/ant-1.6.5.pom.json | 0 .../testfiles/maven/mapper/axis-1.4.pom | 0 .../maven/mapper/axis-1.4.pom.package.json | 0 .../maven/mapper/commons-jaxrs-1.21.pom | 0 .../commons-jaxrs-1.21.pom.package.json | 0 .../maven/mapper/commons-pool-1.5.7.pom | 0 .../commons-pool-1.5.7.pom.package.json | 0 .../maven/mapper/depgraph-view-0.1.pom | 0 .../maven/mapper/maven-all-1.0-RELEASE.pom | 0 .../maven-all-1.0-RELEASE.pom.package.json | 0 .../mapper/mysql-connector-java-5.1.27.pom | 0 ...sql-connector-java-5.1.27.pom.package.json | 0 .../maven/mapper/struts-menu-2.4.2.pom | 0 .../mapper/struts-menu-2.4.2.pom.package.json | 0 .../testfiles/maven/mapper/xbean-jmx-2.0.pom | 0 .../mapper/xbean-jmx-2.0.pom.package.json | 0 .../maven-metadata/expected_maven_xml.json | 0 .../maven/maven-metadata/maven-metadata.xml | 0 .../parsing/empty/common-object-1.0.2.pom | 0 .../common-object-1.0.2.pom.package.json | 0 .../maven/parsing/empty/osgl-http-1.1.2.pom | 0 .../empty/osgl-http-1.1.2.pom.package.json | 0 .../parsing/loop/argus-webservices-2.7.0.pom | 0 .../argus-webservices-2.7.0.pom.package.json | 0 .../parsing/loop/argus-webservices-2.8.0.pom | 0 .../argus-webservices-2.8.0.pom.package.json | 0 .../maven/parsing/loop/coreplugin-1.0.0.pom | 0 .../loop/coreplugin-1.0.0.pom.package.json | 0 .../loop/jacuzzi-annotations-0.2.1.pom | 0 ...jacuzzi-annotations-0.2.1.pom.package.json | 0 .../parsing/loop/jacuzzi-database-0.2.1.pom | 0 .../jacuzzi-database-0.2.1.pom.package.json | 0 .../parsing/loop/ojcms-beans-0.1-beta.pom | 0 .../ojcms-beans-0.1-beta.pom.package.json | 0 .../maven/parsing/loop/pkg-2.0.13.1005.pom | 0 .../loop/pkg-2.0.13.1005.pom.package.json | 0 .../maven/parsing/parse/jds-2.17.0718b.pom | 0 .../parse/jds-2.17.0718b.pom.package.json | 0 .../maven/parsing/parse/jds-3.0.1.pom | 0 .../parsing/parse/jds-3.0.1.pom.package.json | 0 .../parse/maven-javanet-plugin-1.7.pom | 0 .../maven-javanet-plugin-1.7.pom.package.json | 0 ...ringmvc-rest-docs-maven-plugin-1.0-RC1.pom | 0 ...docs-maven-plugin-1.0-RC1.pom.package.json | 0 .../maven/pom/classworlds-1.1-alpha-2.pom | 0 .../tests}/testfiles/npm/0flux.json | 0 .../testfiles/npm/0flux_npm_expected.json | 0 .../tests}/testfiles/npm/1000_records.json | 0 .../tests}/testfiles/npm/2112.json | 0 .../testfiles/npm/29_record_expected.json | 0 .../testfiles/npm/554_record_expected.json | 0 .../testfiles/npm/expected_1000_records.json | 0 .../npm/expected_doclimit_visitor.json | 0 .../npm/expected_npmdownload_data_vistor.json | 0 .../npm/expected_npmdownloadvistor.json | 0 .../npm/expected_npmindexvisitor.json | 0 .../testfiles/npm/expected_over_limit.json | 0 .../testfiles/npm/expected_ticket_439.json | 0 .../testfiles/npm/expected_ticket_440.json | 0 .../tests}/testfiles/npm/grunticon-sass.json | 0 .../testfiles/npm/jsonp-filter-expected.json | 0 .../tests}/testfiles/npm/jsonp-filter.json | 0 .../testfiles/npm/mapper/index.expected.json | 0 .../tests}/testfiles/npm/mapper/index.json | 0 .../npm/microdata-node_expected.json | 0 .../tests}/testfiles/npm/microdata.json | 0 .../testfiles/npm/npm_2112_expected.json | 0 .../tests}/testfiles/npm/over_limit.json | 0 .../tests}/testfiles/npm/replicate_doc1.json | 0 .../tests}/testfiles/npm/ticket_439.json | 0 .../testfiles/npm/ticket_440_records.json | 0 .../testfiles/pypi/boolean.py-2.0.dev3.json | 0 .../tests}/testfiles/pypi/boolean.py.json | 0 .../tests}/testfiles/pypi/cage.json | 0 .../tests}/testfiles/pypi/cage_1.1.2.json | 0 .../tests}/testfiles/pypi/cage_1.1.3.json | 0 .../testfiles/pypi/expected-CAGE-1.1.2.json | 0 .../testfiles/pypi/expected-CAGE-1.1.3.json | 0 .../pypi/expected-boolean.py-2.0.dev3.json | 0 .../testfiles/pypi/expected-lxml-3.2.0.json | 0 .../expected_data-boolean.py-2.0.dev3.json | 0 .../pypi/expected_data-cage_1.1.2.json | 0 .../pypi/expected_data-cage_1.1.3.json | 0 .../testfiles/pypi/expected_uri_visitor1.json | 0 .../testfiles/pypi/expected_uri_visitor2.json | 0 .../expected_uris-boolean.py-2.0.dev3.json | 0 .../pypi/expected_uris-boolean.py.json | 0 .../testfiles/pypi/expected_uris-cage.json | 0 .../pypi/expected_uris-cage_1.1.2.json | 0 .../pypi/expected_uris-cage_1.1.3.json | 0 .../tests}/testfiles/pypi/lxml-3.2.0.json | 0 .../tests}/testfiles/pypi/map/3to2-1.1.1.json | 0 .../pypi/map/expected-3to2-1.1.1.json | 0 .../pypi/pypiindexvisitor-expected.json | 0 .../tests}/testfiles/rsync/rsync_dev.dir | 0 .../testfiles/rsync/rsync_dir/bar/that/baz | 0 .../tests}/testfiles/rsync/rsync_dir/bar/this | 0 .../tests}/testfiles/rsync/rsync_dir/foo | 0 .../tests}/testfiles/rsync/rsync_modules | 0 .../rsync/rsync_v3.0.9_protocol30.dir | 0 .../rsync/rsync_v3.1.0_protocol31.dir | 0 .../tests}/testfiles/rsync/rsync_wicket.dir | 0 .../testfiles/rubygems/0mq-0.4.1.gem.metadata | 0 .../rubygems/0mq-0.4.1.gem.package.json | 0 .../testfiles/rubygems/a_okay-0.1.0.gem | Bin .../rubygems/a_okay-0.1.0.gem.metadata | 0 .../rubygems/a_okay-0.1.0.gem.package.json | 0 .../rubygems/action_tracker-1.0.2.gem | Bin .../action_tracker-1.0.2.gem.package.json | 0 .../rubygems/apiv1/0xffffff.api.json | 0 .../rubygems/apiv1/0xffffff.api.package.json | 0 .../rubygems/apiv1/a1630ty_a1630ty.api.json | 0 .../apiv1/a1630ty_a1630ty.api.mapped.json | 0 .../apiv1/a1630ty_a1630ty.api.package.json | 0 .../rubygems/apiv1/action_tracker.api.json | 0 .../apiv1/action_tracker.api.package.json | 0 .../rubygems/apiv1/expected_0xffffff.api.json | 0 .../apiv1/expected_a1630ty_a1630ty.api.json | 0 .../rubygems/apiv1/expected_zuck.api.json | 0 .../testfiles/rubygems/apiv1/zuck.api.json | 0 .../rubygems/apiv1/zuck.api.package.json | 0 .../rubygems/archive-tar-minitar-0.5.2.gem | Bin ...archive-tar-minitar-0.5.2.gem.package.json | 0 .../testfiles/rubygems/blankslate-3.1.3.gem | Bin .../blankslate-3.1.3.gem.package.json | 0 .../gemspec/address_standardization.gemspec | 0 .../testfiles/rubygems/gemspec/arel.gemspec | 0 .../rubygems/index/latest_specs.4.8.gz | Bin .../index/latest_specs.4.8.gz.expected.json | 0 .../tests}/testfiles/rubygems/m2r-2.1.0.gem | Bin .../rubygems/m2r-2.1.0.gem.package.json | 0 ...mallidea-address_standardization-0.4.1.gem | Bin ...ress_standardization-0.4.1.gem.mapped.json | 0 ...address_standardization-0.4.1.gem.metadata | 0 ...ess_standardization-0.4.1.gem.package.json | 0 .../mysmallidea-mad_mimi_mailer-0.0.9.gem | Bin ...dea-mad_mimi_mailer-0.0.9.gem.package.json | 0 .../rubygems/ng-rails-csrf-0.1.0.gem | Bin .../ng-rails-csrf-0.1.0.gem.package.json | 0 .../tests}/testfiles/rubygems/small-0.2.gem | Bin .../rubygems/small-0.2.gem.package.json | 0 .../rubygems/small_wonder-0.1.10.gem | Bin .../small_wonder-0.1.10.gem.package.json | 0 .../rubygems/sprockets-vendor_gems-0.1.3.gem | Bin ...prockets-vendor_gems-0.1.3.gem.mapped.json | 0 ...rockets-vendor_gems-0.1.3.gem.package.json | 0 ...rockets-vendor_gems-0.1.3.gem.visited.json | 0 ...pdate_with_same_mining_level-expected.json | 0 ...with_higher_new_mining_level-expected.json | 0 ...with_lesser_new_mining_level-expected.json | 0 ...st_merge_packages_no_replace-expected.json | 0 ..._merge_packages_with_replace-expected.json | 0 .../sourceforge/a-vitkus_profile.html | 0 .../expected_heanet_rsync_dir.json | 0 .../sourceforge/expected_netwiki.json | 0 .../sourceforge/expected_sf_dir_index.json | 0 .../sourceforge/expected_sf_dir_page.json | 0 .../sourceforge/expected_sf_project.json | 0 .../sourceforge/expected_sf_sitemap.json | 0 .../sourceforge/expected_sf_sitemap_new.json | 0 .../sourceforge/expected_sf_sitemap_page.json | 0 .../expected_sf_sitemap_page_new.json | 0 .../sourceforge/expected_sitemap-6.json | 0 .../testfiles/sourceforge/filezilla.json | 0 .../sourceforge/mapper_niftyphp_expected.json | 0 .../sourceforge/mapper_odanur_expected.json | 0 .../sourceforge/mapper_omonoql_expected.json | 0 .../mapper_openstunts_expected.json | 0 .../tests}/testfiles/sourceforge/monoql.json | 0 .../tests}/testfiles/sourceforge/netwiki.json | 0 .../testfiles/sourceforge/niftyphp.json | 0 .../tests}/testfiles/sourceforge/odanur.json | 0 .../testfiles/sourceforge/openstunts.json | 0 .../sourceforge/rsync_heanet_sfnet.dir | 0 .../testfiles/sourceforge/sitemap-1.xml | 0 .../testfiles/sourceforge/sitemap-6.xml | 0 .../tests}/testfiles/sourceforge/sitemap.xml | 0 .../src/discovery => discovery}/utils.py | 0 .../src/discovery => discovery}/utils_test.py | 0 .../utils_test.py.ABOUT | 0 .../utils_test.py.NOTICE | 0 .../src/discovery => discovery}/version.py | 0 .../visitors/__init__.py | 0 .../visitors/debian.py | 0 .../visitors/fdroid.py | 0 .../visitors/freebsd.py | 0 .../visitors/java_stream.LICENSE | 0 .../visitors/java_stream.py | 0 .../visitors/java_stream.py.ABOUT | 0 .../discovery => discovery}/visitors/maven.py | 0 .../discovery => discovery}/visitors/npm.py | 0 .../discovery => discovery}/visitors/pypi.py | 0 .../visitors/rubygems.py | 0 .../visitors/sourceforge.py | 0 matchcode/manage.py => manage.py | 2 +- matchcode/.gitignore | 75 --- .../src/clearindex => matchcode}/__init__.py | 0 matchcode/apache-2.0.LICENSE | 201 --------- matchcode/{src/matchcode => }/api.py | 0 .../{src/matchcode => }/fingerprinting.py | 0 matchcode/{src/matchcode => }/halohash.py | 0 matchcode/{src/matchcode => }/hash.py | 0 matchcode/{src/matchcode => }/indexing.py | 0 .../management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/index_packages.py | 0 .../management/commands/match_scan.py | 0 matchcode/{src/matchcode => }/match.py | 0 .../matchcode => }/migrations/0001_initial.py | 0 ...edirectorycontentindex_package_and_more.py | 0 .../migrations}/__init__.py | 0 matchcode/{src/matchcode => }/models.py | 0 matchcode/{src/matchcode => }/plugin_match.py | 0 matchcode/{src/matchcode => }/utils.py | 0 minecode/.gitattributes | 3 - minecode/.gitignore | 73 --- minecode/MANIFEST.in | 21 - minecode/Makefile | 123 ----- minecode/apache-2.0.LICENSE | 201 --------- minecode/cc-by-sa-4.0.LICENSE | 427 ------------------ minecode/configure | 204 --------- minecode/configure.bat | 207 --------- minecode/manage.py | 19 - minecode/pyproject.toml | 47 -- minecode/requirements-dev.txt | 15 - minecode/requirements.txt | 96 ---- minecode/setup.cfg | 79 ---- minecode/setup.py | 6 - minecode/src/minecodeio/settings/__init__.py | 22 - minecode/src/minecodeio/settings/base.py | 157 ------- minecode/src/minecodeio/settings/ci.py | 28 -- minecode/src/minecodeio/settings/dev.py | 27 -- minecode/src/minecodeio/urls.py | 19 - minecode/src/minecodeio/wsgi.py | 27 -- minecode/tests/discovery/__init__.py | 8 - minecode/tests/test_skeleton_codestyle.py | 36 -- packagedb/.gitattributes | 3 - packagedb/.gitignore | 74 --- packagedb/AUTHORS.rst | 7 - packagedb/CHANGELOG.rst | 10 - packagedb/MANIFEST.in | 21 - packagedb/Makefile | 117 ----- packagedb/NOTICE | 12 - packagedb/README.rst | 12 - {minecode/tests => packagedb}/__init__.py | 0 packagedb/apache-2.0.LICENSE | 201 --------- packagedb/{src/packagedb => }/api.py | 0 .../src/matchcode => packagedb}/api_custom.py | 0 packagedb/cc-by-sa-4.0.LICENSE | 427 ------------------ packagedb/configure | 202 --------- packagedb/configure.bat | 207 --------- packagedb/manage.py | 19 - .../packagedb => }/migrations/0001_initial.py | 0 .../migrations/0002_auto_20160707_1018.py | 0 .../migrations/0003_auto_20160708_1513.py | 0 .../migrations/0004_auto_20160713_0022.py | 0 .../migrations/0005_auto_20170217_0309.py | 0 .../migrations/0006_package_mining_level.py | 0 .../migrations/0007_auto_20180713_0144.py | 0 .../migrations/0008_package_package_url.py | 0 .../migrations/0009_auto_20180918_1225.py | 0 .../migrations/0010_auto_20180919_1740.py | 0 .../migrations/0011_auto_20180921_1129.py | 0 .../migrations/0012_auto_20181001_1120.py | 0 .../migrations/0013_auto_20181001_1209.py | 0 .../0014_remove_package_package_url.py | 0 .../0015_remove_package_download_checksums.py | 0 .../migrations/0016_auto_20181023_1211.py | 0 .../migrations/0017_auto_20181023_1211.py | 0 .../migrations/0018_auto_20181023_1212.py | 0 .../migrations/0019_auto_20181023_1212.py | 0 .../0020_package_download_sha256.py | 0 .../0021_package_download_sha512.py | 0 .../migrations/0022_package_manifest_path.py | 0 .../0023_package_source_packages.py | 0 .../migrations/0024_auto_20181030_1817.py | 0 .../migrations/0025_auto_20181030_1817.py | 0 .../migrations/0026_auto_20181030_1824.py | 0 .../migrations/0027_auto_20181030_1825.py | 0 .../migrations/0028_auto_20181127_0224.py | 0 .../migrations/0029_auto_20181127_0246.py | 0 .../migrations/0030_auto_20190107_1616.py | 0 .../migrations/0031_auto_20190110_2354.py | 0 .../migrations/0032_auto_20190125_0019.py | 0 .../migrations/0033_auto_20190128_2056.py | 0 .../migrations/0034_auto_20200407_2232.py | 0 .../migrations/0035_auto_20200408_2126.py | 0 .../migrations/0036_auto_20200416_2131.py | 0 .../migrations/0037_auto_20200423_1242.py | 0 .../0038_add_index_for_filter_fields.py | 0 .../0039_packageurl_python_field_updates.py | 0 .../migrations/0040_add_root_path.py | 0 .../migrations/0041_update_ordering_to_id.py | 0 ...0042_update_fields_to_django3_standards.py | 0 .../migrations/0043_lowercase_purl_fields.py | 0 .../migrations/0044_add_history_field.py | 0 ...nse_expression_max_length_for_resources.py | 0 .../0046_add_extra_data_to_package.py | 0 ...0047_add_search_vector_field_to_package.py | 0 ...48_add_gin_index_to_search_vector_field.py | 0 .../0049_alter_resource_extra_data.py | 0 .../0050_alter_resource_extra_data.py | 0 ...data_url_package_datasource_id_and_more.py | 0 ...e_index_error_package_last_indexed_date.py | 0 .../migrations/__init__.py | 0 packagedb/{src/packagedb => }/models.py | 0 packagedb/pyproject.toml | 54 --- packagedb/requirements-dev.txt | 26 -- packagedb/requirements.txt | 11 - packagedb/{src/packagedb => }/serializers.py | 0 packagedb/setup.cfg | 70 --- packagedb/setup.py | 6 - packagedb/{src/packagedb => }/signals.py | 0 packagedb/src/packagedb/__init__.py | 8 - packagedb/src/packagedb/api_custom.py | 22 - packagedb/src/packagedbio/__init__.py | 8 - packagedb/src/packagedbio/settings.py | 263 ----------- packagedb/src/packagedbio/static/.keep | 0 packagedb/src/packagedbio/urls.py | 25 - packagedb/src/packagedbio/wsgi.py | 23 - packagedb/tests/packagedb/__init__.py | 8 - packagedb/tests/packagedb/tests/__init__.py | 8 - .../tests/{packagedb/tests => }/test_api.py | 0 .../{packagedb/tests => }/test_models.py | 0 packagedb/tests/test_skeleton_codestyle.py | 36 -- .../testfiles/ant-commons-logging-1.6.1.jar | Bin .../index/spring-boot-1.3.0.RELEASE.jar | Bin .../index/spring-boot-1.3.1.RELEASE.jar | Bin .../index/spring-boot-1.3.2.RELEASE.jar | Bin .../index/spring-boot-1.3.3.RELEASE.jar | Bin .../index/spring-boot-1.3.4.RELEASE.jar | Bin .../index/spring-boot-1.3.5.RELEASE.jar | Bin .../index/spring-boot-1.3.6.RELEASE.jar | Bin .../index/spring-boot-1.3.7.RELEASE.jar | Bin .../index/spring-boot-1.3.8.RELEASE.jar | Bin .../match_jars/ant-commons-logging-1.6.1.jar | Bin .../match_jars/spring-boot-1.3.8.RELEASE.jar | Bin .../migrations => purldb}/__init__.py | 0 .../src/matchcodeio => purldb}/settings.py | 41 +- .../src/matchcodeio => purldb}/static/.keep | 0 {matchcode/src/matchcodeio => purldb}/urls.py | 0 {matchcode/src/matchcodeio => purldb}/wsgi.py | 0 matchcode/pyproject.toml => pyproject.toml | 0 ...quirements-dev.txt => requirements-dev.txt | 0 .../requirements.txt => requirements.txt | 0 matchcode/setup.cfg => setup.cfg | 26 +- matchcode/setup.py => setup.py | 0 553 files changed, 74 insertions(+), 4029 deletions(-) rename matchcode/.gitattributes => .gitattributes (100%) rename matchcode/MANIFEST.in => MANIFEST.in (100%) rename matchcode/Makefile => Makefile (97%) rename {matchcode/src/matchcode => clearindex}/__init__.py (100%) rename {minecode/src/clearindex => clearindex}/harvest.py (100%) rename {matchcode/src/matchcode => clearindex}/management/__init__.py (100%) rename {matchcode/src/matchcode/migrations => clearindex/management/commands}/__init__.py (100%) rename {minecode/src/clearindex => clearindex}/management/commands/run_clearindex.py (100%) rename {minecode/src/clearindex => clearindex}/utils.py (100%) rename matchcode/configure => configure (91%) rename {minecode => discovery}/AUTHORS.rst (100%) rename {minecode => discovery}/CHANGELOG.rst (100%) rename {minecode => discovery}/NOTICE (100%) rename {minecode => discovery}/README.rst (100%) rename {minecode/src/discovery => discovery}/__init__.py (100%) rename {minecode/src/discovery => discovery}/api.py (100%) rename {minecode/src/discovery => discovery}/apps.py (100%) rename {minecode/src/discovery => discovery}/bsd-new.LICENSE (100%) rename {minecode/src/discovery => discovery}/command.py (100%) rename {minecode/src/discovery => discovery}/debutils.py (100%) rename {minecode/src/discovery => discovery}/ls.py (100%) rename {minecode/src/discovery => discovery}/management/__init__.py (100%) rename {minecode/src/discovery => discovery}/management/commands/__init__.py (100%) rename {minecode/src/discovery => discovery}/management/commands/check_licenses.py (100%) rename {minecode/src/discovery => discovery}/management/commands/check_uri.py (100%) rename {minecode/src/discovery => discovery}/management/commands/dump_purls.py (100%) rename {minecode/src/discovery => discovery}/management/commands/get_status.py (100%) rename {minecode/src/discovery => discovery}/management/commands/remap.py (100%) rename {minecode/src/discovery => discovery}/management/commands/run_map.py (100%) rename {minecode/src/discovery => discovery}/management/commands/run_visit.py (100%) rename {minecode/src/discovery => discovery}/management/commands/seed.py (100%) rename {minecode/src/discovery => discovery}/mappers/__init__.py (100%) rename {minecode/src/discovery => discovery}/mappers/debian.py (100%) rename {minecode/src/discovery => discovery}/mappers/fdroid.py (100%) rename {minecode/src/discovery => discovery}/mappers/freebsd.py (100%) rename {minecode/src/discovery => discovery}/mappers/maven.py (100%) rename {minecode/src/discovery => discovery}/mappers/npm.py (100%) rename {minecode/src/discovery => discovery}/mappers/pypi.py (100%) rename {minecode/src/discovery => discovery}/mappers/rubygems.py (100%) rename {minecode/src/discovery => discovery}/mappers/sourceforge.py (100%) rename {minecode/src/discovery => discovery}/mappings/__init__.py (100%) rename {minecode/src/discovery => discovery}/mappings/pypi_trove.py (100%) rename {minecode/src/discovery => discovery}/migrations/0001_initial.py (100%) rename {minecode/src/discovery => discovery}/migrations/0002_auto_20160707_1249.py (100%) rename {minecode/src/discovery => discovery}/migrations/0003_auto_20160712_1223.py (100%) rename {minecode/src/discovery => discovery}/migrations/0004_auto_20160713_1731.py (100%) rename {minecode/src/discovery => discovery}/migrations/0005_resourceuri_metadata.py (100%) rename {minecode/src/discovery => discovery}/migrations/0006_remove_resourceuri_metadata.py (100%) rename {minecode/src/discovery => discovery}/migrations/0007_change_unique_constraints_on_resourceuri.py (100%) rename {minecode/src/discovery => discovery}/migrations/0008_change_default_sort_order_on_resourceuri.py (100%) rename {minecode/src/discovery => discovery}/migrations/0009_resourceuri_source_uri.py (100%) rename {minecode/src/discovery => discovery}/migrations/0010_resourceuri_mining_level.py (100%) rename {minecode/src/discovery => discovery}/migrations/0011_auto_20170807_0253.py (100%) rename {minecode/src/discovery => discovery}/migrations/0012_auto_20170807_0444.py (100%) rename {minecode/src/discovery => discovery}/migrations/0013_auto_20170807_0511.py (100%) rename {minecode/src/discovery => discovery}/migrations/0014_auto_20170807_0529.py (100%) rename {minecode/src/discovery => discovery}/migrations/0015_auto_20180607_0851.py (100%) rename {minecode/src/discovery => discovery}/migrations/0016_remove_resourceuri_sig.py (100%) rename {minecode/src/discovery => discovery}/migrations/0017_auto_20180619_2236.py (100%) rename {minecode/src/discovery => discovery}/migrations/0018_scannableuri_package_id.py (100%) rename {minecode/src/discovery => discovery}/migrations/0019_auto_20180716_1648.py (100%) rename {minecode/src/discovery => discovery}/migrations/0020_resourceuri_package_url.py (100%) rename {minecode/src/discovery => discovery}/migrations/0021_auto_20181026_1635.py (100%) rename {minecode/src/discovery => discovery}/migrations/0022_auto_20190307_2332.py (100%) rename {matchcode/src/matchcodeio => discovery/migrations}/__init__.py (100%) rename {minecode/src/discovery => discovery}/models.py (100%) rename {minecode/src/discovery => discovery}/route.py (100%) rename {minecode/src/discovery => discovery}/rsync.py (100%) rename {minecode/src/discovery => discovery}/saneyaml.py (100%) rename {minecode/src/discovery => discovery}/seed.py (100%) rename {minecode/src/minecodeio => discovery/tests}/__init__.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_command.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_debian.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_fdroid.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_freebsd.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_housekeeping.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_ls.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_maven.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_models.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_npm.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_pypi.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_route.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_rsync.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_rubygems.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_run_map.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_run_visit.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_seed.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_sourceforge.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_utils.py (100%) rename {minecode/tests/discovery => discovery/tests}/test_version.py (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/command/bar (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/command/foo/.gitignore (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/copyright/basic_copyright (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/copyright/copyright_abiword_common_copyright-abiword_common_copyright.copyright (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/copyright/invalid_copyright (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/debian_lslrs_expected (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/debian_lslrs_on_ubuntu_expected (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/debian_sourceindex_expected (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc-expected (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/debutils/control_basic (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/debutils/control_invalid (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/dsc/7kaa_2.14.3-1.dsc (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/dsc/description-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/dsc/description.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/dsc/description_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/dsc/invalid.dsc (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/invalid_files/ls-lR.gz (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/lslr/ls-lR_debian.gz (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/lslr/ls-lR_debian.gz-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/lslr/ls-lR_ubuntu.gz (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/lslr/ls-lR_ubuntu.gz-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/packages/debian_Packages (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/packages/debian_Packages-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/packages/debian_Packages-visit-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/packages/ubuntu_Packages (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/packages/ubuntu_Packages-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/release/Release (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/release/Release_expected (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/release/Release_with_md5 (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/release/Release_with_md5_expected (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/release/visited_Release (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/release/visited_Release-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/sources/Sources.gz (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/sources/Sources.gz-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/sources/debian_Sources (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/sources/debian_Sources_mapped-expected-packages.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/sources/debian_Sources_visit_expected (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/sources/ubuntu_Sources (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/sources/ubuntu_Sources_visit_expected (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/debian/status/simple_status (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/directories/find-ls (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/directories/find-ls-apache-start (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/directories/find-ls-apache-start-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/directories/find-ls-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/directories/ls-lr (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/directories/ls-lr-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/directories/ls-lr-ubuntu (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/directories/ls-lr-ubuntu-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/fdroid/index-v2-expected-visit.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/fdroid/index-v2-visited-expected-mapped.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/fdroid/index-v2-visited.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/fdroid/index-v2.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/freebsd/FreeBSD-10-i386_release_0_.html (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/freebsd/FreeBSD-10-i386_release_0_.html_expected (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/freebsd/FreeBSD.org.html (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/freebsd/FreeBSD.org.html_expected (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/freebsd/indexfile_expected (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/freebsd/indexfile_expected_mapper.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/freebsd/mapper_input1 (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/freebsd/packagesite.txz (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/freebsd/pkg-devel_index (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/freebsd/pkg-devel_index_mapper.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/freebsd/squirrelmail-plugins-1.0_2.txz (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom_search_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/housekeeping/declared_license_search_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/housekeeping/example_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/housekeeping/ignore_upper_case_search_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/housekeeping/license_expression_search_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end/bytejta-supports-0.5.0-ALPHA4.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end/expected_mapped_packages.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end/expected_visited_uris.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end/test_uris.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end_index/expected_visited_increment_index.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end_index/expected_visited_index.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end_index/nexus-maven-repository-index.163.gz (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end_index/nexus-maven-repository-index.properties (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-index-data.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-pom-data.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-index.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-pom.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end_unicode/commons-jaxrs-1.22.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end_unicode/expected_mapped_commons-jaxrs-1.22.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/end2end_unicode/expected_visited_commons-jaxrs-1.22.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/html/app.html (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/html/jcenter.bintray.com.html (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/html/stateframework-compiler.html (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/html/visitor_expected_app.html.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/html/visitor_expected_jcenter.bintray.com2.html.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/html/visitor_expected_stateframework-compiler.html.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/buggy/expected_artifacts-defaults.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/buggy/expected_artifacts.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/buggy/expected_entries.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/buggy/expected_uris.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/buggy/expected_visited_uris.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/buggy/nexus-maven-repository-index.gz (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/expected_artifacts-all-worthy.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/expected_artifacts-defaults.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/expected_artifacts.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/expected_entries.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/expected_uris.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/increment/expected_artifacts-defaults.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/increment/expected_artifacts.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/increment/expected_entries.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/increment/expected_properties_uris.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/increment/expected_uris.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/increment/nexus-maven-repository-index.445.gz (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/increment/nexus-maven-repository-index.properties (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/increment2/expected_mini_package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/increment2/expected_uris.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/increment2/nexus-maven-repository-index.457.gz (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/index/nexus-maven-repository-index.gz (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/ant-1.6.5.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/ant-1.6.5.pom.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/axis-1.4.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/axis-1.4.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/commons-jaxrs-1.21.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/commons-jaxrs-1.21.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/commons-pool-1.5.7.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/commons-pool-1.5.7.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/depgraph-view-0.1.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/struts-menu-2.4.2.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/struts-menu-2.4.2.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/xbean-jmx-2.0.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/mapper/xbean-jmx-2.0.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/maven-metadata/expected_maven_xml.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/maven-metadata/maven-metadata.xml (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/empty/common-object-1.0.2.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/empty/common-object-1.0.2.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/parse/jds-2.17.0718b.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/parse/jds-2.17.0718b.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/parse/jds-3.0.1.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/parse/jds-3.0.1.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/maven/pom/classworlds-1.1-alpha-2.pom (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/0flux.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/0flux_npm_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/1000_records.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/2112.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/29_record_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/554_record_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/expected_1000_records.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/expected_doclimit_visitor.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/expected_npmdownload_data_vistor.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/expected_npmdownloadvistor.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/expected_npmindexvisitor.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/expected_over_limit.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/expected_ticket_439.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/expected_ticket_440.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/grunticon-sass.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/jsonp-filter-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/jsonp-filter.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/mapper/index.expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/mapper/index.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/microdata-node_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/microdata.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/npm_2112_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/over_limit.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/replicate_doc1.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/ticket_439.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/npm/ticket_440_records.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/boolean.py-2.0.dev3.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/boolean.py.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/cage.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/cage_1.1.2.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/cage_1.1.3.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/expected-CAGE-1.1.2.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/expected-CAGE-1.1.3.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/expected-boolean.py-2.0.dev3.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/expected-lxml-3.2.0.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/expected_data-boolean.py-2.0.dev3.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/expected_data-cage_1.1.2.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/expected_data-cage_1.1.3.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/expected_uri_visitor1.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/expected_uri_visitor2.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/expected_uris-boolean.py-2.0.dev3.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/expected_uris-boolean.py.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/expected_uris-cage.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/expected_uris-cage_1.1.2.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/expected_uris-cage_1.1.3.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/lxml-3.2.0.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/map/3to2-1.1.1.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/map/expected-3to2-1.1.1.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/pypi/pypiindexvisitor-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rsync/rsync_dev.dir (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rsync/rsync_dir/bar/that/baz (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rsync/rsync_dir/bar/this (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rsync/rsync_dir/foo (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rsync/rsync_modules (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rsync/rsync_v3.0.9_protocol30.dir (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rsync/rsync_v3.1.0_protocol31.dir (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rsync/rsync_wicket.dir (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/0mq-0.4.1.gem.metadata (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/0mq-0.4.1.gem.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/a_okay-0.1.0.gem (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/a_okay-0.1.0.gem.metadata (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/a_okay-0.1.0.gem.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/action_tracker-1.0.2.gem (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/action_tracker-1.0.2.gem.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/apiv1/0xffffff.api.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/apiv1/0xffffff.api.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.mapped.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/apiv1/action_tracker.api.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/apiv1/action_tracker.api.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/apiv1/expected_0xffffff.api.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/apiv1/expected_a1630ty_a1630ty.api.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/apiv1/expected_zuck.api.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/apiv1/zuck.api.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/apiv1/zuck.api.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/archive-tar-minitar-0.5.2.gem (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/archive-tar-minitar-0.5.2.gem.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/blankslate-3.1.3.gem (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/blankslate-3.1.3.gem.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/gemspec/address_standardization.gemspec (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/gemspec/arel.gemspec (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/index/latest_specs.4.8.gz (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/index/latest_specs.4.8.gz.expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/m2r-2.1.0.gem (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/m2r-2.1.0.gem.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.mapped.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.metadata (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/ng-rails-csrf-0.1.0.gem (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/ng-rails-csrf-0.1.0.gem.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/small-0.2.gem (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/small-0.2.gem.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/small_wonder-0.1.10.gem (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/small_wonder-0.1.10.gem.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.mapped.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.package.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.visited.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/run_map/test_map_uri_does_update_with_same_mining_level-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/run_map/test_map_uri_replace_with_new_with_higher_new_mining_level-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/run_map/test_map_uri_update_only_empties_with_lesser_new_mining_level-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/run_map/test_merge_packages_no_replace-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/run_map/test_merge_packages_with_replace-expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/a-vitkus_profile.html (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/expected_heanet_rsync_dir.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/expected_netwiki.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/expected_sf_dir_index.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/expected_sf_dir_page.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/expected_sf_project.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/expected_sf_sitemap.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/expected_sf_sitemap_new.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/expected_sf_sitemap_page.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/expected_sf_sitemap_page_new.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/expected_sitemap-6.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/filezilla.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/mapper_niftyphp_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/mapper_odanur_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/mapper_omonoql_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/mapper_openstunts_expected.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/monoql.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/netwiki.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/niftyphp.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/odanur.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/openstunts.json (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/rsync_heanet_sfnet.dir (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/sitemap-1.xml (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/sitemap-6.xml (100%) rename {minecode/tests/discovery => discovery/tests}/testfiles/sourceforge/sitemap.xml (100%) rename {minecode/src/discovery => discovery}/utils.py (100%) rename {minecode/src/discovery => discovery}/utils_test.py (100%) rename {minecode/src/discovery => discovery}/utils_test.py.ABOUT (100%) rename {minecode/src/discovery => discovery}/utils_test.py.NOTICE (100%) rename {minecode/src/discovery => discovery}/version.py (100%) rename {minecode/src/discovery => discovery}/visitors/__init__.py (100%) rename {minecode/src/discovery => discovery}/visitors/debian.py (100%) rename {minecode/src/discovery => discovery}/visitors/fdroid.py (100%) rename {minecode/src/discovery => discovery}/visitors/freebsd.py (100%) rename {minecode/src/discovery => discovery}/visitors/java_stream.LICENSE (100%) rename {minecode/src/discovery => discovery}/visitors/java_stream.py (100%) rename {minecode/src/discovery => discovery}/visitors/java_stream.py.ABOUT (100%) rename {minecode/src/discovery => discovery}/visitors/maven.py (100%) rename {minecode/src/discovery => discovery}/visitors/npm.py (100%) rename {minecode/src/discovery => discovery}/visitors/pypi.py (100%) rename {minecode/src/discovery => discovery}/visitors/rubygems.py (100%) rename {minecode/src/discovery => discovery}/visitors/sourceforge.py (100%) rename matchcode/manage.py => manage.py (87%) delete mode 100644 matchcode/.gitignore rename {minecode/src/clearindex => matchcode}/__init__.py (100%) delete mode 100644 matchcode/apache-2.0.LICENSE rename matchcode/{src/matchcode => }/api.py (100%) rename matchcode/{src/matchcode => }/fingerprinting.py (100%) rename matchcode/{src/matchcode => }/halohash.py (100%) rename matchcode/{src/matchcode => }/hash.py (100%) rename matchcode/{src/matchcode => }/indexing.py (100%) rename {minecode/src/clearindex => matchcode}/management/__init__.py (100%) rename matchcode/{src/matchcode => }/management/commands/__init__.py (100%) rename matchcode/{src/matchcode => }/management/commands/index_packages.py (100%) rename matchcode/{src/matchcode => }/management/commands/match_scan.py (100%) rename matchcode/{src/matchcode => }/match.py (100%) rename matchcode/{src/matchcode => }/migrations/0001_initial.py (100%) rename matchcode/{src/matchcode => }/migrations/0002_alter_approximatedirectorycontentindex_package_and_more.py (100%) rename {minecode/src/clearindex/management/commands => matchcode/migrations}/__init__.py (100%) rename matchcode/{src/matchcode => }/models.py (100%) rename matchcode/{src/matchcode => }/plugin_match.py (100%) rename matchcode/{src/matchcode => }/utils.py (100%) delete mode 100644 minecode/.gitattributes delete mode 100644 minecode/.gitignore delete mode 100644 minecode/MANIFEST.in delete mode 100644 minecode/Makefile delete mode 100644 minecode/apache-2.0.LICENSE delete mode 100644 minecode/cc-by-sa-4.0.LICENSE delete mode 100755 minecode/configure delete mode 100644 minecode/configure.bat delete mode 100755 minecode/manage.py delete mode 100644 minecode/pyproject.toml delete mode 100644 minecode/requirements-dev.txt delete mode 100644 minecode/requirements.txt delete mode 100644 minecode/setup.cfg delete mode 100644 minecode/setup.py delete mode 100644 minecode/src/minecodeio/settings/__init__.py delete mode 100644 minecode/src/minecodeio/settings/base.py delete mode 100644 minecode/src/minecodeio/settings/ci.py delete mode 100644 minecode/src/minecodeio/settings/dev.py delete mode 100644 minecode/src/minecodeio/urls.py delete mode 100644 minecode/src/minecodeio/wsgi.py delete mode 100644 minecode/tests/discovery/__init__.py delete mode 100644 minecode/tests/test_skeleton_codestyle.py delete mode 100644 packagedb/.gitattributes delete mode 100644 packagedb/.gitignore delete mode 100644 packagedb/AUTHORS.rst delete mode 100644 packagedb/CHANGELOG.rst delete mode 100644 packagedb/MANIFEST.in delete mode 100644 packagedb/Makefile delete mode 100644 packagedb/NOTICE delete mode 100644 packagedb/README.rst rename {minecode/tests => packagedb}/__init__.py (100%) delete mode 100644 packagedb/apache-2.0.LICENSE rename packagedb/{src/packagedb => }/api.py (100%) rename {matchcode/src/matchcode => packagedb}/api_custom.py (100%) delete mode 100644 packagedb/cc-by-sa-4.0.LICENSE delete mode 100755 packagedb/configure delete mode 100644 packagedb/configure.bat delete mode 100755 packagedb/manage.py rename packagedb/{src/packagedb => }/migrations/0001_initial.py (100%) rename packagedb/{src/packagedb => }/migrations/0002_auto_20160707_1018.py (100%) rename packagedb/{src/packagedb => }/migrations/0003_auto_20160708_1513.py (100%) rename packagedb/{src/packagedb => }/migrations/0004_auto_20160713_0022.py (100%) rename packagedb/{src/packagedb => }/migrations/0005_auto_20170217_0309.py (100%) rename packagedb/{src/packagedb => }/migrations/0006_package_mining_level.py (100%) rename packagedb/{src/packagedb => }/migrations/0007_auto_20180713_0144.py (100%) rename packagedb/{src/packagedb => }/migrations/0008_package_package_url.py (100%) rename packagedb/{src/packagedb => }/migrations/0009_auto_20180918_1225.py (100%) rename packagedb/{src/packagedb => }/migrations/0010_auto_20180919_1740.py (100%) rename packagedb/{src/packagedb => }/migrations/0011_auto_20180921_1129.py (100%) rename packagedb/{src/packagedb => }/migrations/0012_auto_20181001_1120.py (100%) rename packagedb/{src/packagedb => }/migrations/0013_auto_20181001_1209.py (100%) rename packagedb/{src/packagedb => }/migrations/0014_remove_package_package_url.py (100%) rename packagedb/{src/packagedb => }/migrations/0015_remove_package_download_checksums.py (100%) rename packagedb/{src/packagedb => }/migrations/0016_auto_20181023_1211.py (100%) rename packagedb/{src/packagedb => }/migrations/0017_auto_20181023_1211.py (100%) rename packagedb/{src/packagedb => }/migrations/0018_auto_20181023_1212.py (100%) rename packagedb/{src/packagedb => }/migrations/0019_auto_20181023_1212.py (100%) rename packagedb/{src/packagedb => }/migrations/0020_package_download_sha256.py (100%) rename packagedb/{src/packagedb => }/migrations/0021_package_download_sha512.py (100%) rename packagedb/{src/packagedb => }/migrations/0022_package_manifest_path.py (100%) rename packagedb/{src/packagedb => }/migrations/0023_package_source_packages.py (100%) rename packagedb/{src/packagedb => }/migrations/0024_auto_20181030_1817.py (100%) rename packagedb/{src/packagedb => }/migrations/0025_auto_20181030_1817.py (100%) rename packagedb/{src/packagedb => }/migrations/0026_auto_20181030_1824.py (100%) rename packagedb/{src/packagedb => }/migrations/0027_auto_20181030_1825.py (100%) rename packagedb/{src/packagedb => }/migrations/0028_auto_20181127_0224.py (100%) rename packagedb/{src/packagedb => }/migrations/0029_auto_20181127_0246.py (100%) rename packagedb/{src/packagedb => }/migrations/0030_auto_20190107_1616.py (100%) rename packagedb/{src/packagedb => }/migrations/0031_auto_20190110_2354.py (100%) rename packagedb/{src/packagedb => }/migrations/0032_auto_20190125_0019.py (100%) rename packagedb/{src/packagedb => }/migrations/0033_auto_20190128_2056.py (100%) rename packagedb/{src/packagedb => }/migrations/0034_auto_20200407_2232.py (100%) rename packagedb/{src/packagedb => }/migrations/0035_auto_20200408_2126.py (100%) rename packagedb/{src/packagedb => }/migrations/0036_auto_20200416_2131.py (100%) rename packagedb/{src/packagedb => }/migrations/0037_auto_20200423_1242.py (100%) rename packagedb/{src/packagedb => }/migrations/0038_add_index_for_filter_fields.py (100%) rename packagedb/{src/packagedb => }/migrations/0039_packageurl_python_field_updates.py (100%) rename packagedb/{src/packagedb => }/migrations/0040_add_root_path.py (100%) rename packagedb/{src/packagedb => }/migrations/0041_update_ordering_to_id.py (100%) rename packagedb/{src/packagedb => }/migrations/0042_update_fields_to_django3_standards.py (100%) rename packagedb/{src/packagedb => }/migrations/0043_lowercase_purl_fields.py (100%) rename packagedb/{src/packagedb => }/migrations/0044_add_history_field.py (100%) rename packagedb/{src/packagedb => }/migrations/0045_relax_license_expression_max_length_for_resources.py (100%) rename packagedb/{src/packagedb => }/migrations/0046_add_extra_data_to_package.py (100%) rename packagedb/{src/packagedb => }/migrations/0047_add_search_vector_field_to_package.py (100%) rename packagedb/{src/packagedb => }/migrations/0048_add_gin_index_to_search_vector_field.py (100%) rename packagedb/{src/packagedb => }/migrations/0049_alter_resource_extra_data.py (100%) rename packagedb/{src/packagedb => }/migrations/0050_alter_resource_extra_data.py (100%) rename packagedb/{src/packagedb => }/migrations/0051_package_api_data_url_package_datasource_id_and_more.py (100%) rename packagedb/{src/packagedb => }/migrations/0052_package_index_error_package_last_indexed_date.py (100%) rename {minecode/src/discovery => packagedb}/migrations/__init__.py (100%) rename packagedb/{src/packagedb => }/models.py (100%) delete mode 100644 packagedb/pyproject.toml delete mode 100644 packagedb/requirements-dev.txt delete mode 100644 packagedb/requirements.txt rename packagedb/{src/packagedb => }/serializers.py (100%) delete mode 100644 packagedb/setup.cfg delete mode 100644 packagedb/setup.py rename packagedb/{src/packagedb => }/signals.py (100%) delete mode 100644 packagedb/src/packagedb/__init__.py delete mode 100644 packagedb/src/packagedb/api_custom.py delete mode 100644 packagedb/src/packagedbio/__init__.py delete mode 100644 packagedb/src/packagedbio/settings.py delete mode 100644 packagedb/src/packagedbio/static/.keep delete mode 100644 packagedb/src/packagedbio/urls.py delete mode 100644 packagedb/src/packagedbio/wsgi.py delete mode 100644 packagedb/tests/packagedb/__init__.py delete mode 100644 packagedb/tests/packagedb/tests/__init__.py rename packagedb/tests/{packagedb/tests => }/test_api.py (100%) rename packagedb/tests/{packagedb/tests => }/test_models.py (100%) delete mode 100644 packagedb/tests/test_skeleton_codestyle.py rename packagedb/tests/{packagedb/tests => }/testfiles/ant-commons-logging-1.6.1.jar (100%) rename packagedb/tests/{packagedb/tests => }/testfiles/index/spring-boot-1.3.0.RELEASE.jar (100%) rename packagedb/tests/{packagedb/tests => }/testfiles/index/spring-boot-1.3.1.RELEASE.jar (100%) rename packagedb/tests/{packagedb/tests => }/testfiles/index/spring-boot-1.3.2.RELEASE.jar (100%) rename packagedb/tests/{packagedb/tests => }/testfiles/index/spring-boot-1.3.3.RELEASE.jar (100%) rename packagedb/tests/{packagedb/tests => }/testfiles/index/spring-boot-1.3.4.RELEASE.jar (100%) rename packagedb/tests/{packagedb/tests => }/testfiles/index/spring-boot-1.3.5.RELEASE.jar (100%) rename packagedb/tests/{packagedb/tests => }/testfiles/index/spring-boot-1.3.6.RELEASE.jar (100%) rename packagedb/tests/{packagedb/tests => }/testfiles/index/spring-boot-1.3.7.RELEASE.jar (100%) rename packagedb/tests/{packagedb/tests => }/testfiles/index/spring-boot-1.3.8.RELEASE.jar (100%) rename packagedb/tests/{packagedb/tests => }/testfiles/match_jars/ant-commons-logging-1.6.1.jar (100%) rename packagedb/tests/{packagedb/tests => }/testfiles/match_jars/spring-boot-1.3.8.RELEASE.jar (100%) rename {packagedb/src/packagedb/migrations => purldb}/__init__.py (100%) rename {matchcode/src/matchcodeio => purldb}/settings.py (86%) rename {matchcode/src/matchcodeio => purldb}/static/.keep (100%) rename {matchcode/src/matchcodeio => purldb}/urls.py (100%) rename {matchcode/src/matchcodeio => purldb}/wsgi.py (100%) rename matchcode/pyproject.toml => pyproject.toml (100%) rename matchcode/requirements-dev.txt => requirements-dev.txt (100%) rename matchcode/requirements.txt => requirements.txt (100%) rename matchcode/setup.cfg => setup.cfg (76%) rename matchcode/setup.py => setup.py (100%) diff --git a/matchcode/.gitattributes b/.gitattributes similarity index 100% rename from matchcode/.gitattributes rename to .gitattributes diff --git a/matchcode/MANIFEST.in b/MANIFEST.in similarity index 100% rename from matchcode/MANIFEST.in rename to MANIFEST.in diff --git a/matchcode/Makefile b/Makefile similarity index 97% rename from matchcode/Makefile rename to Makefile index b0c8d981..c074eb20 100644 --- a/matchcode/Makefile +++ b/Makefile @@ -92,7 +92,7 @@ run: test: @echo "-> Run the test suite" - ${ACTIVATE} DJANGO_SETTINGS_MODULE=matchcodeio.settings ${PYTHON_EXE} -m pytest -vvs + ${ACTIVATE} DJANGO_SETTINGS_MODULE=purldb.settings ${PYTHON_EXE} -m pytest -vvs shell: ${MANAGE} shell diff --git a/apache-2.0.LICENSE b/apache-2.0.LICENSE index d9a10c0d..261eeb9e 100644 --- a/apache-2.0.LICENSE +++ b/apache-2.0.LICENSE @@ -174,3 +174,28 @@ of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/matchcode/src/matchcode/__init__.py b/clearindex/__init__.py similarity index 100% rename from matchcode/src/matchcode/__init__.py rename to clearindex/__init__.py diff --git a/minecode/src/clearindex/harvest.py b/clearindex/harvest.py similarity index 100% rename from minecode/src/clearindex/harvest.py rename to clearindex/harvest.py diff --git a/matchcode/src/matchcode/management/__init__.py b/clearindex/management/__init__.py similarity index 100% rename from matchcode/src/matchcode/management/__init__.py rename to clearindex/management/__init__.py diff --git a/matchcode/src/matchcode/migrations/__init__.py b/clearindex/management/commands/__init__.py similarity index 100% rename from matchcode/src/matchcode/migrations/__init__.py rename to clearindex/management/commands/__init__.py diff --git a/minecode/src/clearindex/management/commands/run_clearindex.py b/clearindex/management/commands/run_clearindex.py similarity index 100% rename from minecode/src/clearindex/management/commands/run_clearindex.py rename to clearindex/management/commands/run_clearindex.py diff --git a/minecode/src/clearindex/utils.py b/clearindex/utils.py similarity index 100% rename from minecode/src/clearindex/utils.py rename to clearindex/utils.py diff --git a/matchcode/configure b/configure similarity index 91% rename from matchcode/configure rename to configure index 18352055..006e891a 100755 --- a/matchcode/configure +++ b/configure @@ -30,9 +30,9 @@ CLI_ARGS=$1 CUSTOM_PACKAGES="https://github.com/nexB/commoncode/archive/refs/heads/48-correctly-assign-codebase-attributes.zip https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip" # Requirement arguments passed to pip and used by default or with --dev. -REQUIREMENTS="$CUSTOM_PACKAGES --editable ../clearcode-toolkit --editable ../packagedb --editable ../minecode --editable . --constraint requirements.txt" -DEV_REQUIREMENTS="$CUSTOM_PACKAGES --editable ../clearcode-toolkit --editable ../packagedb[testing] --editable ../minecode[testing] --editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt" -DOCS_REQUIREMENTS="$CUSTOM_PACKAGES --editable ../clearcode-toolkit --editable ../packagedb[docs] --editable ../minecode[docs] --editable .[docs] --constraint requirements.txt" +REQUIREMENTS="$CUSTOM_PACKAGES --editable ./clearcode-toolkit --editable . --constraint requirements.txt" +DEV_REQUIREMENTS="$CUSTOM_PACKAGES --editable ./clearcode-toolkit --editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt" +DOCS_REQUIREMENTS="$CUSTOM_PACKAGES --editable ./clearcode-toolkit --editable .[docs] --constraint requirements.txt" # where we create a virtualenv VIRTUALENV_DIR=venv diff --git a/minecode/AUTHORS.rst b/discovery/AUTHORS.rst similarity index 100% rename from minecode/AUTHORS.rst rename to discovery/AUTHORS.rst diff --git a/minecode/CHANGELOG.rst b/discovery/CHANGELOG.rst similarity index 100% rename from minecode/CHANGELOG.rst rename to discovery/CHANGELOG.rst diff --git a/minecode/NOTICE b/discovery/NOTICE similarity index 100% rename from minecode/NOTICE rename to discovery/NOTICE diff --git a/minecode/README.rst b/discovery/README.rst similarity index 100% rename from minecode/README.rst rename to discovery/README.rst diff --git a/minecode/src/discovery/__init__.py b/discovery/__init__.py similarity index 100% rename from minecode/src/discovery/__init__.py rename to discovery/__init__.py diff --git a/minecode/src/discovery/api.py b/discovery/api.py similarity index 100% rename from minecode/src/discovery/api.py rename to discovery/api.py diff --git a/minecode/src/discovery/apps.py b/discovery/apps.py similarity index 100% rename from minecode/src/discovery/apps.py rename to discovery/apps.py diff --git a/minecode/src/discovery/bsd-new.LICENSE b/discovery/bsd-new.LICENSE similarity index 100% rename from minecode/src/discovery/bsd-new.LICENSE rename to discovery/bsd-new.LICENSE diff --git a/minecode/src/discovery/command.py b/discovery/command.py similarity index 100% rename from minecode/src/discovery/command.py rename to discovery/command.py diff --git a/minecode/src/discovery/debutils.py b/discovery/debutils.py similarity index 100% rename from minecode/src/discovery/debutils.py rename to discovery/debutils.py diff --git a/minecode/src/discovery/ls.py b/discovery/ls.py similarity index 100% rename from minecode/src/discovery/ls.py rename to discovery/ls.py diff --git a/minecode/src/discovery/management/__init__.py b/discovery/management/__init__.py similarity index 100% rename from minecode/src/discovery/management/__init__.py rename to discovery/management/__init__.py diff --git a/minecode/src/discovery/management/commands/__init__.py b/discovery/management/commands/__init__.py similarity index 100% rename from minecode/src/discovery/management/commands/__init__.py rename to discovery/management/commands/__init__.py diff --git a/minecode/src/discovery/management/commands/check_licenses.py b/discovery/management/commands/check_licenses.py similarity index 100% rename from minecode/src/discovery/management/commands/check_licenses.py rename to discovery/management/commands/check_licenses.py diff --git a/minecode/src/discovery/management/commands/check_uri.py b/discovery/management/commands/check_uri.py similarity index 100% rename from minecode/src/discovery/management/commands/check_uri.py rename to discovery/management/commands/check_uri.py diff --git a/minecode/src/discovery/management/commands/dump_purls.py b/discovery/management/commands/dump_purls.py similarity index 100% rename from minecode/src/discovery/management/commands/dump_purls.py rename to discovery/management/commands/dump_purls.py diff --git a/minecode/src/discovery/management/commands/get_status.py b/discovery/management/commands/get_status.py similarity index 100% rename from minecode/src/discovery/management/commands/get_status.py rename to discovery/management/commands/get_status.py diff --git a/minecode/src/discovery/management/commands/remap.py b/discovery/management/commands/remap.py similarity index 100% rename from minecode/src/discovery/management/commands/remap.py rename to discovery/management/commands/remap.py diff --git a/minecode/src/discovery/management/commands/run_map.py b/discovery/management/commands/run_map.py similarity index 100% rename from minecode/src/discovery/management/commands/run_map.py rename to discovery/management/commands/run_map.py diff --git a/minecode/src/discovery/management/commands/run_visit.py b/discovery/management/commands/run_visit.py similarity index 100% rename from minecode/src/discovery/management/commands/run_visit.py rename to discovery/management/commands/run_visit.py diff --git a/minecode/src/discovery/management/commands/seed.py b/discovery/management/commands/seed.py similarity index 100% rename from minecode/src/discovery/management/commands/seed.py rename to discovery/management/commands/seed.py diff --git a/minecode/src/discovery/mappers/__init__.py b/discovery/mappers/__init__.py similarity index 100% rename from minecode/src/discovery/mappers/__init__.py rename to discovery/mappers/__init__.py diff --git a/minecode/src/discovery/mappers/debian.py b/discovery/mappers/debian.py similarity index 100% rename from minecode/src/discovery/mappers/debian.py rename to discovery/mappers/debian.py diff --git a/minecode/src/discovery/mappers/fdroid.py b/discovery/mappers/fdroid.py similarity index 100% rename from minecode/src/discovery/mappers/fdroid.py rename to discovery/mappers/fdroid.py diff --git a/minecode/src/discovery/mappers/freebsd.py b/discovery/mappers/freebsd.py similarity index 100% rename from minecode/src/discovery/mappers/freebsd.py rename to discovery/mappers/freebsd.py diff --git a/minecode/src/discovery/mappers/maven.py b/discovery/mappers/maven.py similarity index 100% rename from minecode/src/discovery/mappers/maven.py rename to discovery/mappers/maven.py diff --git a/minecode/src/discovery/mappers/npm.py b/discovery/mappers/npm.py similarity index 100% rename from minecode/src/discovery/mappers/npm.py rename to discovery/mappers/npm.py diff --git a/minecode/src/discovery/mappers/pypi.py b/discovery/mappers/pypi.py similarity index 100% rename from minecode/src/discovery/mappers/pypi.py rename to discovery/mappers/pypi.py diff --git a/minecode/src/discovery/mappers/rubygems.py b/discovery/mappers/rubygems.py similarity index 100% rename from minecode/src/discovery/mappers/rubygems.py rename to discovery/mappers/rubygems.py diff --git a/minecode/src/discovery/mappers/sourceforge.py b/discovery/mappers/sourceforge.py similarity index 100% rename from minecode/src/discovery/mappers/sourceforge.py rename to discovery/mappers/sourceforge.py diff --git a/minecode/src/discovery/mappings/__init__.py b/discovery/mappings/__init__.py similarity index 100% rename from minecode/src/discovery/mappings/__init__.py rename to discovery/mappings/__init__.py diff --git a/minecode/src/discovery/mappings/pypi_trove.py b/discovery/mappings/pypi_trove.py similarity index 100% rename from minecode/src/discovery/mappings/pypi_trove.py rename to discovery/mappings/pypi_trove.py diff --git a/minecode/src/discovery/migrations/0001_initial.py b/discovery/migrations/0001_initial.py similarity index 100% rename from minecode/src/discovery/migrations/0001_initial.py rename to discovery/migrations/0001_initial.py diff --git a/minecode/src/discovery/migrations/0002_auto_20160707_1249.py b/discovery/migrations/0002_auto_20160707_1249.py similarity index 100% rename from minecode/src/discovery/migrations/0002_auto_20160707_1249.py rename to discovery/migrations/0002_auto_20160707_1249.py diff --git a/minecode/src/discovery/migrations/0003_auto_20160712_1223.py b/discovery/migrations/0003_auto_20160712_1223.py similarity index 100% rename from minecode/src/discovery/migrations/0003_auto_20160712_1223.py rename to discovery/migrations/0003_auto_20160712_1223.py diff --git a/minecode/src/discovery/migrations/0004_auto_20160713_1731.py b/discovery/migrations/0004_auto_20160713_1731.py similarity index 100% rename from minecode/src/discovery/migrations/0004_auto_20160713_1731.py rename to discovery/migrations/0004_auto_20160713_1731.py diff --git a/minecode/src/discovery/migrations/0005_resourceuri_metadata.py b/discovery/migrations/0005_resourceuri_metadata.py similarity index 100% rename from minecode/src/discovery/migrations/0005_resourceuri_metadata.py rename to discovery/migrations/0005_resourceuri_metadata.py diff --git a/minecode/src/discovery/migrations/0006_remove_resourceuri_metadata.py b/discovery/migrations/0006_remove_resourceuri_metadata.py similarity index 100% rename from minecode/src/discovery/migrations/0006_remove_resourceuri_metadata.py rename to discovery/migrations/0006_remove_resourceuri_metadata.py diff --git a/minecode/src/discovery/migrations/0007_change_unique_constraints_on_resourceuri.py b/discovery/migrations/0007_change_unique_constraints_on_resourceuri.py similarity index 100% rename from minecode/src/discovery/migrations/0007_change_unique_constraints_on_resourceuri.py rename to discovery/migrations/0007_change_unique_constraints_on_resourceuri.py diff --git a/minecode/src/discovery/migrations/0008_change_default_sort_order_on_resourceuri.py b/discovery/migrations/0008_change_default_sort_order_on_resourceuri.py similarity index 100% rename from minecode/src/discovery/migrations/0008_change_default_sort_order_on_resourceuri.py rename to discovery/migrations/0008_change_default_sort_order_on_resourceuri.py diff --git a/minecode/src/discovery/migrations/0009_resourceuri_source_uri.py b/discovery/migrations/0009_resourceuri_source_uri.py similarity index 100% rename from minecode/src/discovery/migrations/0009_resourceuri_source_uri.py rename to discovery/migrations/0009_resourceuri_source_uri.py diff --git a/minecode/src/discovery/migrations/0010_resourceuri_mining_level.py b/discovery/migrations/0010_resourceuri_mining_level.py similarity index 100% rename from minecode/src/discovery/migrations/0010_resourceuri_mining_level.py rename to discovery/migrations/0010_resourceuri_mining_level.py diff --git a/minecode/src/discovery/migrations/0011_auto_20170807_0253.py b/discovery/migrations/0011_auto_20170807_0253.py similarity index 100% rename from minecode/src/discovery/migrations/0011_auto_20170807_0253.py rename to discovery/migrations/0011_auto_20170807_0253.py diff --git a/minecode/src/discovery/migrations/0012_auto_20170807_0444.py b/discovery/migrations/0012_auto_20170807_0444.py similarity index 100% rename from minecode/src/discovery/migrations/0012_auto_20170807_0444.py rename to discovery/migrations/0012_auto_20170807_0444.py diff --git a/minecode/src/discovery/migrations/0013_auto_20170807_0511.py b/discovery/migrations/0013_auto_20170807_0511.py similarity index 100% rename from minecode/src/discovery/migrations/0013_auto_20170807_0511.py rename to discovery/migrations/0013_auto_20170807_0511.py diff --git a/minecode/src/discovery/migrations/0014_auto_20170807_0529.py b/discovery/migrations/0014_auto_20170807_0529.py similarity index 100% rename from minecode/src/discovery/migrations/0014_auto_20170807_0529.py rename to discovery/migrations/0014_auto_20170807_0529.py diff --git a/minecode/src/discovery/migrations/0015_auto_20180607_0851.py b/discovery/migrations/0015_auto_20180607_0851.py similarity index 100% rename from minecode/src/discovery/migrations/0015_auto_20180607_0851.py rename to discovery/migrations/0015_auto_20180607_0851.py diff --git a/minecode/src/discovery/migrations/0016_remove_resourceuri_sig.py b/discovery/migrations/0016_remove_resourceuri_sig.py similarity index 100% rename from minecode/src/discovery/migrations/0016_remove_resourceuri_sig.py rename to discovery/migrations/0016_remove_resourceuri_sig.py diff --git a/minecode/src/discovery/migrations/0017_auto_20180619_2236.py b/discovery/migrations/0017_auto_20180619_2236.py similarity index 100% rename from minecode/src/discovery/migrations/0017_auto_20180619_2236.py rename to discovery/migrations/0017_auto_20180619_2236.py diff --git a/minecode/src/discovery/migrations/0018_scannableuri_package_id.py b/discovery/migrations/0018_scannableuri_package_id.py similarity index 100% rename from minecode/src/discovery/migrations/0018_scannableuri_package_id.py rename to discovery/migrations/0018_scannableuri_package_id.py diff --git a/minecode/src/discovery/migrations/0019_auto_20180716_1648.py b/discovery/migrations/0019_auto_20180716_1648.py similarity index 100% rename from minecode/src/discovery/migrations/0019_auto_20180716_1648.py rename to discovery/migrations/0019_auto_20180716_1648.py diff --git a/minecode/src/discovery/migrations/0020_resourceuri_package_url.py b/discovery/migrations/0020_resourceuri_package_url.py similarity index 100% rename from minecode/src/discovery/migrations/0020_resourceuri_package_url.py rename to discovery/migrations/0020_resourceuri_package_url.py diff --git a/minecode/src/discovery/migrations/0021_auto_20181026_1635.py b/discovery/migrations/0021_auto_20181026_1635.py similarity index 100% rename from minecode/src/discovery/migrations/0021_auto_20181026_1635.py rename to discovery/migrations/0021_auto_20181026_1635.py diff --git a/minecode/src/discovery/migrations/0022_auto_20190307_2332.py b/discovery/migrations/0022_auto_20190307_2332.py similarity index 100% rename from minecode/src/discovery/migrations/0022_auto_20190307_2332.py rename to discovery/migrations/0022_auto_20190307_2332.py diff --git a/matchcode/src/matchcodeio/__init__.py b/discovery/migrations/__init__.py similarity index 100% rename from matchcode/src/matchcodeio/__init__.py rename to discovery/migrations/__init__.py diff --git a/minecode/src/discovery/models.py b/discovery/models.py similarity index 100% rename from minecode/src/discovery/models.py rename to discovery/models.py diff --git a/minecode/src/discovery/route.py b/discovery/route.py similarity index 100% rename from minecode/src/discovery/route.py rename to discovery/route.py diff --git a/minecode/src/discovery/rsync.py b/discovery/rsync.py similarity index 100% rename from minecode/src/discovery/rsync.py rename to discovery/rsync.py diff --git a/minecode/src/discovery/saneyaml.py b/discovery/saneyaml.py similarity index 100% rename from minecode/src/discovery/saneyaml.py rename to discovery/saneyaml.py diff --git a/minecode/src/discovery/seed.py b/discovery/seed.py similarity index 100% rename from minecode/src/discovery/seed.py rename to discovery/seed.py diff --git a/minecode/src/minecodeio/__init__.py b/discovery/tests/__init__.py similarity index 100% rename from minecode/src/minecodeio/__init__.py rename to discovery/tests/__init__.py diff --git a/minecode/tests/discovery/test_command.py b/discovery/tests/test_command.py similarity index 100% rename from minecode/tests/discovery/test_command.py rename to discovery/tests/test_command.py diff --git a/minecode/tests/discovery/test_debian.py b/discovery/tests/test_debian.py similarity index 100% rename from minecode/tests/discovery/test_debian.py rename to discovery/tests/test_debian.py diff --git a/minecode/tests/discovery/test_fdroid.py b/discovery/tests/test_fdroid.py similarity index 100% rename from minecode/tests/discovery/test_fdroid.py rename to discovery/tests/test_fdroid.py diff --git a/minecode/tests/discovery/test_freebsd.py b/discovery/tests/test_freebsd.py similarity index 100% rename from minecode/tests/discovery/test_freebsd.py rename to discovery/tests/test_freebsd.py diff --git a/minecode/tests/discovery/test_housekeeping.py b/discovery/tests/test_housekeeping.py similarity index 100% rename from minecode/tests/discovery/test_housekeeping.py rename to discovery/tests/test_housekeeping.py diff --git a/minecode/tests/discovery/test_ls.py b/discovery/tests/test_ls.py similarity index 100% rename from minecode/tests/discovery/test_ls.py rename to discovery/tests/test_ls.py diff --git a/minecode/tests/discovery/test_maven.py b/discovery/tests/test_maven.py similarity index 100% rename from minecode/tests/discovery/test_maven.py rename to discovery/tests/test_maven.py diff --git a/minecode/tests/discovery/test_models.py b/discovery/tests/test_models.py similarity index 100% rename from minecode/tests/discovery/test_models.py rename to discovery/tests/test_models.py diff --git a/minecode/tests/discovery/test_npm.py b/discovery/tests/test_npm.py similarity index 100% rename from minecode/tests/discovery/test_npm.py rename to discovery/tests/test_npm.py diff --git a/minecode/tests/discovery/test_pypi.py b/discovery/tests/test_pypi.py similarity index 100% rename from minecode/tests/discovery/test_pypi.py rename to discovery/tests/test_pypi.py diff --git a/minecode/tests/discovery/test_route.py b/discovery/tests/test_route.py similarity index 100% rename from minecode/tests/discovery/test_route.py rename to discovery/tests/test_route.py diff --git a/minecode/tests/discovery/test_rsync.py b/discovery/tests/test_rsync.py similarity index 100% rename from minecode/tests/discovery/test_rsync.py rename to discovery/tests/test_rsync.py diff --git a/minecode/tests/discovery/test_rubygems.py b/discovery/tests/test_rubygems.py similarity index 100% rename from minecode/tests/discovery/test_rubygems.py rename to discovery/tests/test_rubygems.py diff --git a/minecode/tests/discovery/test_run_map.py b/discovery/tests/test_run_map.py similarity index 100% rename from minecode/tests/discovery/test_run_map.py rename to discovery/tests/test_run_map.py diff --git a/minecode/tests/discovery/test_run_visit.py b/discovery/tests/test_run_visit.py similarity index 100% rename from minecode/tests/discovery/test_run_visit.py rename to discovery/tests/test_run_visit.py diff --git a/minecode/tests/discovery/test_seed.py b/discovery/tests/test_seed.py similarity index 100% rename from minecode/tests/discovery/test_seed.py rename to discovery/tests/test_seed.py diff --git a/minecode/tests/discovery/test_sourceforge.py b/discovery/tests/test_sourceforge.py similarity index 100% rename from minecode/tests/discovery/test_sourceforge.py rename to discovery/tests/test_sourceforge.py diff --git a/minecode/tests/discovery/test_utils.py b/discovery/tests/test_utils.py similarity index 100% rename from minecode/tests/discovery/test_utils.py rename to discovery/tests/test_utils.py diff --git a/minecode/tests/discovery/test_version.py b/discovery/tests/test_version.py similarity index 100% rename from minecode/tests/discovery/test_version.py rename to discovery/tests/test_version.py diff --git a/minecode/tests/discovery/testfiles/command/bar b/discovery/tests/testfiles/command/bar similarity index 100% rename from minecode/tests/discovery/testfiles/command/bar rename to discovery/tests/testfiles/command/bar diff --git a/minecode/tests/discovery/testfiles/command/foo/.gitignore b/discovery/tests/testfiles/command/foo/.gitignore similarity index 100% rename from minecode/tests/discovery/testfiles/command/foo/.gitignore rename to discovery/tests/testfiles/command/foo/.gitignore diff --git a/minecode/tests/discovery/testfiles/debian/copyright/basic_copyright b/discovery/tests/testfiles/debian/copyright/basic_copyright similarity index 100% rename from minecode/tests/discovery/testfiles/debian/copyright/basic_copyright rename to discovery/tests/testfiles/debian/copyright/basic_copyright diff --git a/minecode/tests/discovery/testfiles/debian/copyright/copyright_abiword_common_copyright-abiword_common_copyright.copyright b/discovery/tests/testfiles/debian/copyright/copyright_abiword_common_copyright-abiword_common_copyright.copyright similarity index 100% rename from minecode/tests/discovery/testfiles/debian/copyright/copyright_abiword_common_copyright-abiword_common_copyright.copyright rename to discovery/tests/testfiles/debian/copyright/copyright_abiword_common_copyright-abiword_common_copyright.copyright diff --git a/minecode/tests/discovery/testfiles/debian/copyright/invalid_copyright b/discovery/tests/testfiles/debian/copyright/invalid_copyright similarity index 100% rename from minecode/tests/discovery/testfiles/debian/copyright/invalid_copyright rename to discovery/tests/testfiles/debian/copyright/invalid_copyright diff --git a/minecode/tests/discovery/testfiles/debian/debian_lslrs_expected b/discovery/tests/testfiles/debian/debian_lslrs_expected similarity index 100% rename from minecode/tests/discovery/testfiles/debian/debian_lslrs_expected rename to discovery/tests/testfiles/debian/debian_lslrs_expected diff --git a/minecode/tests/discovery/testfiles/debian/debian_lslrs_on_ubuntu_expected b/discovery/tests/testfiles/debian/debian_lslrs_on_ubuntu_expected similarity index 100% rename from minecode/tests/discovery/testfiles/debian/debian_lslrs_on_ubuntu_expected rename to discovery/tests/testfiles/debian/debian_lslrs_on_ubuntu_expected diff --git a/minecode/tests/discovery/testfiles/debian/debian_sourceindex_expected b/discovery/tests/testfiles/debian/debian_sourceindex_expected similarity index 100% rename from minecode/tests/discovery/testfiles/debian/debian_sourceindex_expected rename to discovery/tests/testfiles/debian/debian_sourceindex_expected diff --git a/minecode/tests/discovery/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc b/discovery/tests/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc similarity index 100% rename from minecode/tests/discovery/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc rename to discovery/tests/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc diff --git a/minecode/tests/discovery/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc-expected b/discovery/tests/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc-expected similarity index 100% rename from minecode/tests/discovery/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc-expected rename to discovery/tests/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc-expected diff --git a/minecode/tests/discovery/testfiles/debian/debutils/control_basic b/discovery/tests/testfiles/debian/debutils/control_basic similarity index 100% rename from minecode/tests/discovery/testfiles/debian/debutils/control_basic rename to discovery/tests/testfiles/debian/debutils/control_basic diff --git a/minecode/tests/discovery/testfiles/debian/debutils/control_invalid b/discovery/tests/testfiles/debian/debutils/control_invalid similarity index 100% rename from minecode/tests/discovery/testfiles/debian/debutils/control_invalid rename to discovery/tests/testfiles/debian/debutils/control_invalid diff --git a/minecode/tests/discovery/testfiles/debian/dsc/7kaa_2.14.3-1.dsc b/discovery/tests/testfiles/debian/dsc/7kaa_2.14.3-1.dsc similarity index 100% rename from minecode/tests/discovery/testfiles/debian/dsc/7kaa_2.14.3-1.dsc rename to discovery/tests/testfiles/debian/dsc/7kaa_2.14.3-1.dsc diff --git a/minecode/tests/discovery/testfiles/debian/dsc/description-expected.json b/discovery/tests/testfiles/debian/dsc/description-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/debian/dsc/description-expected.json rename to discovery/tests/testfiles/debian/dsc/description-expected.json diff --git a/minecode/tests/discovery/testfiles/debian/dsc/description.json b/discovery/tests/testfiles/debian/dsc/description.json similarity index 100% rename from minecode/tests/discovery/testfiles/debian/dsc/description.json rename to discovery/tests/testfiles/debian/dsc/description.json diff --git a/minecode/tests/discovery/testfiles/debian/dsc/description_expected.json b/discovery/tests/testfiles/debian/dsc/description_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/debian/dsc/description_expected.json rename to discovery/tests/testfiles/debian/dsc/description_expected.json diff --git a/minecode/tests/discovery/testfiles/debian/dsc/invalid.dsc b/discovery/tests/testfiles/debian/dsc/invalid.dsc similarity index 100% rename from minecode/tests/discovery/testfiles/debian/dsc/invalid.dsc rename to discovery/tests/testfiles/debian/dsc/invalid.dsc diff --git a/minecode/tests/discovery/testfiles/debian/invalid_files/ls-lR.gz b/discovery/tests/testfiles/debian/invalid_files/ls-lR.gz similarity index 100% rename from minecode/tests/discovery/testfiles/debian/invalid_files/ls-lR.gz rename to discovery/tests/testfiles/debian/invalid_files/ls-lR.gz diff --git a/minecode/tests/discovery/testfiles/debian/lslr/ls-lR_debian.gz b/discovery/tests/testfiles/debian/lslr/ls-lR_debian.gz similarity index 100% rename from minecode/tests/discovery/testfiles/debian/lslr/ls-lR_debian.gz rename to discovery/tests/testfiles/debian/lslr/ls-lR_debian.gz diff --git a/minecode/tests/discovery/testfiles/debian/lslr/ls-lR_debian.gz-expected.json b/discovery/tests/testfiles/debian/lslr/ls-lR_debian.gz-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/debian/lslr/ls-lR_debian.gz-expected.json rename to discovery/tests/testfiles/debian/lslr/ls-lR_debian.gz-expected.json diff --git a/minecode/tests/discovery/testfiles/debian/lslr/ls-lR_ubuntu.gz b/discovery/tests/testfiles/debian/lslr/ls-lR_ubuntu.gz similarity index 100% rename from minecode/tests/discovery/testfiles/debian/lslr/ls-lR_ubuntu.gz rename to discovery/tests/testfiles/debian/lslr/ls-lR_ubuntu.gz diff --git a/minecode/tests/discovery/testfiles/debian/lslr/ls-lR_ubuntu.gz-expected.json b/discovery/tests/testfiles/debian/lslr/ls-lR_ubuntu.gz-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/debian/lslr/ls-lR_ubuntu.gz-expected.json rename to discovery/tests/testfiles/debian/lslr/ls-lR_ubuntu.gz-expected.json diff --git a/minecode/tests/discovery/testfiles/debian/packages/debian_Packages b/discovery/tests/testfiles/debian/packages/debian_Packages similarity index 100% rename from minecode/tests/discovery/testfiles/debian/packages/debian_Packages rename to discovery/tests/testfiles/debian/packages/debian_Packages diff --git a/minecode/tests/discovery/testfiles/debian/packages/debian_Packages-expected.json b/discovery/tests/testfiles/debian/packages/debian_Packages-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/debian/packages/debian_Packages-expected.json rename to discovery/tests/testfiles/debian/packages/debian_Packages-expected.json diff --git a/minecode/tests/discovery/testfiles/debian/packages/debian_Packages-visit-expected.json b/discovery/tests/testfiles/debian/packages/debian_Packages-visit-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/debian/packages/debian_Packages-visit-expected.json rename to discovery/tests/testfiles/debian/packages/debian_Packages-visit-expected.json diff --git a/minecode/tests/discovery/testfiles/debian/packages/ubuntu_Packages b/discovery/tests/testfiles/debian/packages/ubuntu_Packages similarity index 100% rename from minecode/tests/discovery/testfiles/debian/packages/ubuntu_Packages rename to discovery/tests/testfiles/debian/packages/ubuntu_Packages diff --git a/minecode/tests/discovery/testfiles/debian/packages/ubuntu_Packages-expected.json b/discovery/tests/testfiles/debian/packages/ubuntu_Packages-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/debian/packages/ubuntu_Packages-expected.json rename to discovery/tests/testfiles/debian/packages/ubuntu_Packages-expected.json diff --git a/minecode/tests/discovery/testfiles/debian/release/Release b/discovery/tests/testfiles/debian/release/Release similarity index 100% rename from minecode/tests/discovery/testfiles/debian/release/Release rename to discovery/tests/testfiles/debian/release/Release diff --git a/minecode/tests/discovery/testfiles/debian/release/Release_expected b/discovery/tests/testfiles/debian/release/Release_expected similarity index 100% rename from minecode/tests/discovery/testfiles/debian/release/Release_expected rename to discovery/tests/testfiles/debian/release/Release_expected diff --git a/minecode/tests/discovery/testfiles/debian/release/Release_with_md5 b/discovery/tests/testfiles/debian/release/Release_with_md5 similarity index 100% rename from minecode/tests/discovery/testfiles/debian/release/Release_with_md5 rename to discovery/tests/testfiles/debian/release/Release_with_md5 diff --git a/minecode/tests/discovery/testfiles/debian/release/Release_with_md5_expected b/discovery/tests/testfiles/debian/release/Release_with_md5_expected similarity index 100% rename from minecode/tests/discovery/testfiles/debian/release/Release_with_md5_expected rename to discovery/tests/testfiles/debian/release/Release_with_md5_expected diff --git a/minecode/tests/discovery/testfiles/debian/release/visited_Release b/discovery/tests/testfiles/debian/release/visited_Release similarity index 100% rename from minecode/tests/discovery/testfiles/debian/release/visited_Release rename to discovery/tests/testfiles/debian/release/visited_Release diff --git a/minecode/tests/discovery/testfiles/debian/release/visited_Release-expected.json b/discovery/tests/testfiles/debian/release/visited_Release-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/debian/release/visited_Release-expected.json rename to discovery/tests/testfiles/debian/release/visited_Release-expected.json diff --git a/minecode/tests/discovery/testfiles/debian/sources/Sources.gz b/discovery/tests/testfiles/debian/sources/Sources.gz similarity index 100% rename from minecode/tests/discovery/testfiles/debian/sources/Sources.gz rename to discovery/tests/testfiles/debian/sources/Sources.gz diff --git a/minecode/tests/discovery/testfiles/debian/sources/Sources.gz-expected.json b/discovery/tests/testfiles/debian/sources/Sources.gz-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/debian/sources/Sources.gz-expected.json rename to discovery/tests/testfiles/debian/sources/Sources.gz-expected.json diff --git a/minecode/tests/discovery/testfiles/debian/sources/debian_Sources b/discovery/tests/testfiles/debian/sources/debian_Sources similarity index 100% rename from minecode/tests/discovery/testfiles/debian/sources/debian_Sources rename to discovery/tests/testfiles/debian/sources/debian_Sources diff --git a/minecode/tests/discovery/testfiles/debian/sources/debian_Sources_mapped-expected-packages.json b/discovery/tests/testfiles/debian/sources/debian_Sources_mapped-expected-packages.json similarity index 100% rename from minecode/tests/discovery/testfiles/debian/sources/debian_Sources_mapped-expected-packages.json rename to discovery/tests/testfiles/debian/sources/debian_Sources_mapped-expected-packages.json diff --git a/minecode/tests/discovery/testfiles/debian/sources/debian_Sources_visit_expected b/discovery/tests/testfiles/debian/sources/debian_Sources_visit_expected similarity index 100% rename from minecode/tests/discovery/testfiles/debian/sources/debian_Sources_visit_expected rename to discovery/tests/testfiles/debian/sources/debian_Sources_visit_expected diff --git a/minecode/tests/discovery/testfiles/debian/sources/ubuntu_Sources b/discovery/tests/testfiles/debian/sources/ubuntu_Sources similarity index 100% rename from minecode/tests/discovery/testfiles/debian/sources/ubuntu_Sources rename to discovery/tests/testfiles/debian/sources/ubuntu_Sources diff --git a/minecode/tests/discovery/testfiles/debian/sources/ubuntu_Sources_visit_expected b/discovery/tests/testfiles/debian/sources/ubuntu_Sources_visit_expected similarity index 100% rename from minecode/tests/discovery/testfiles/debian/sources/ubuntu_Sources_visit_expected rename to discovery/tests/testfiles/debian/sources/ubuntu_Sources_visit_expected diff --git a/minecode/tests/discovery/testfiles/debian/status/simple_status b/discovery/tests/testfiles/debian/status/simple_status similarity index 100% rename from minecode/tests/discovery/testfiles/debian/status/simple_status rename to discovery/tests/testfiles/debian/status/simple_status diff --git a/minecode/tests/discovery/testfiles/directories/find-ls b/discovery/tests/testfiles/directories/find-ls similarity index 100% rename from minecode/tests/discovery/testfiles/directories/find-ls rename to discovery/tests/testfiles/directories/find-ls diff --git a/minecode/tests/discovery/testfiles/directories/find-ls-apache-start b/discovery/tests/testfiles/directories/find-ls-apache-start similarity index 100% rename from minecode/tests/discovery/testfiles/directories/find-ls-apache-start rename to discovery/tests/testfiles/directories/find-ls-apache-start diff --git a/minecode/tests/discovery/testfiles/directories/find-ls-apache-start-expected.json b/discovery/tests/testfiles/directories/find-ls-apache-start-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/directories/find-ls-apache-start-expected.json rename to discovery/tests/testfiles/directories/find-ls-apache-start-expected.json diff --git a/minecode/tests/discovery/testfiles/directories/find-ls-expected.json b/discovery/tests/testfiles/directories/find-ls-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/directories/find-ls-expected.json rename to discovery/tests/testfiles/directories/find-ls-expected.json diff --git a/minecode/tests/discovery/testfiles/directories/ls-lr b/discovery/tests/testfiles/directories/ls-lr similarity index 100% rename from minecode/tests/discovery/testfiles/directories/ls-lr rename to discovery/tests/testfiles/directories/ls-lr diff --git a/minecode/tests/discovery/testfiles/directories/ls-lr-expected.json b/discovery/tests/testfiles/directories/ls-lr-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/directories/ls-lr-expected.json rename to discovery/tests/testfiles/directories/ls-lr-expected.json diff --git a/minecode/tests/discovery/testfiles/directories/ls-lr-ubuntu b/discovery/tests/testfiles/directories/ls-lr-ubuntu similarity index 100% rename from minecode/tests/discovery/testfiles/directories/ls-lr-ubuntu rename to discovery/tests/testfiles/directories/ls-lr-ubuntu diff --git a/minecode/tests/discovery/testfiles/directories/ls-lr-ubuntu-expected.json b/discovery/tests/testfiles/directories/ls-lr-ubuntu-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/directories/ls-lr-ubuntu-expected.json rename to discovery/tests/testfiles/directories/ls-lr-ubuntu-expected.json diff --git a/minecode/tests/discovery/testfiles/fdroid/index-v2-expected-visit.json b/discovery/tests/testfiles/fdroid/index-v2-expected-visit.json similarity index 100% rename from minecode/tests/discovery/testfiles/fdroid/index-v2-expected-visit.json rename to discovery/tests/testfiles/fdroid/index-v2-expected-visit.json diff --git a/minecode/tests/discovery/testfiles/fdroid/index-v2-visited-expected-mapped.json b/discovery/tests/testfiles/fdroid/index-v2-visited-expected-mapped.json similarity index 100% rename from minecode/tests/discovery/testfiles/fdroid/index-v2-visited-expected-mapped.json rename to discovery/tests/testfiles/fdroid/index-v2-visited-expected-mapped.json diff --git a/minecode/tests/discovery/testfiles/fdroid/index-v2-visited.json b/discovery/tests/testfiles/fdroid/index-v2-visited.json similarity index 100% rename from minecode/tests/discovery/testfiles/fdroid/index-v2-visited.json rename to discovery/tests/testfiles/fdroid/index-v2-visited.json diff --git a/minecode/tests/discovery/testfiles/fdroid/index-v2.json b/discovery/tests/testfiles/fdroid/index-v2.json similarity index 100% rename from minecode/tests/discovery/testfiles/fdroid/index-v2.json rename to discovery/tests/testfiles/fdroid/index-v2.json diff --git a/minecode/tests/discovery/testfiles/freebsd/FreeBSD-10-i386_release_0_.html b/discovery/tests/testfiles/freebsd/FreeBSD-10-i386_release_0_.html similarity index 100% rename from minecode/tests/discovery/testfiles/freebsd/FreeBSD-10-i386_release_0_.html rename to discovery/tests/testfiles/freebsd/FreeBSD-10-i386_release_0_.html diff --git a/minecode/tests/discovery/testfiles/freebsd/FreeBSD-10-i386_release_0_.html_expected b/discovery/tests/testfiles/freebsd/FreeBSD-10-i386_release_0_.html_expected similarity index 100% rename from minecode/tests/discovery/testfiles/freebsd/FreeBSD-10-i386_release_0_.html_expected rename to discovery/tests/testfiles/freebsd/FreeBSD-10-i386_release_0_.html_expected diff --git a/minecode/tests/discovery/testfiles/freebsd/FreeBSD.org.html b/discovery/tests/testfiles/freebsd/FreeBSD.org.html similarity index 100% rename from minecode/tests/discovery/testfiles/freebsd/FreeBSD.org.html rename to discovery/tests/testfiles/freebsd/FreeBSD.org.html diff --git a/minecode/tests/discovery/testfiles/freebsd/FreeBSD.org.html_expected b/discovery/tests/testfiles/freebsd/FreeBSD.org.html_expected similarity index 100% rename from minecode/tests/discovery/testfiles/freebsd/FreeBSD.org.html_expected rename to discovery/tests/testfiles/freebsd/FreeBSD.org.html_expected diff --git a/minecode/tests/discovery/testfiles/freebsd/indexfile_expected b/discovery/tests/testfiles/freebsd/indexfile_expected similarity index 100% rename from minecode/tests/discovery/testfiles/freebsd/indexfile_expected rename to discovery/tests/testfiles/freebsd/indexfile_expected diff --git a/minecode/tests/discovery/testfiles/freebsd/indexfile_expected_mapper.json b/discovery/tests/testfiles/freebsd/indexfile_expected_mapper.json similarity index 100% rename from minecode/tests/discovery/testfiles/freebsd/indexfile_expected_mapper.json rename to discovery/tests/testfiles/freebsd/indexfile_expected_mapper.json diff --git a/minecode/tests/discovery/testfiles/freebsd/mapper_input1 b/discovery/tests/testfiles/freebsd/mapper_input1 similarity index 100% rename from minecode/tests/discovery/testfiles/freebsd/mapper_input1 rename to discovery/tests/testfiles/freebsd/mapper_input1 diff --git a/minecode/tests/discovery/testfiles/freebsd/packagesite.txz b/discovery/tests/testfiles/freebsd/packagesite.txz similarity index 100% rename from minecode/tests/discovery/testfiles/freebsd/packagesite.txz rename to discovery/tests/testfiles/freebsd/packagesite.txz diff --git a/minecode/tests/discovery/testfiles/freebsd/pkg-devel_index b/discovery/tests/testfiles/freebsd/pkg-devel_index similarity index 100% rename from minecode/tests/discovery/testfiles/freebsd/pkg-devel_index rename to discovery/tests/testfiles/freebsd/pkg-devel_index diff --git a/minecode/tests/discovery/testfiles/freebsd/pkg-devel_index_mapper.json b/discovery/tests/testfiles/freebsd/pkg-devel_index_mapper.json similarity index 100% rename from minecode/tests/discovery/testfiles/freebsd/pkg-devel_index_mapper.json rename to discovery/tests/testfiles/freebsd/pkg-devel_index_mapper.json diff --git a/minecode/tests/discovery/testfiles/freebsd/squirrelmail-plugins-1.0_2.txz b/discovery/tests/testfiles/freebsd/squirrelmail-plugins-1.0_2.txz similarity index 100% rename from minecode/tests/discovery/testfiles/freebsd/squirrelmail-plugins-1.0_2.txz rename to discovery/tests/testfiles/freebsd/squirrelmail-plugins-1.0_2.txz diff --git a/minecode/tests/discovery/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom b/discovery/tests/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom similarity index 100% rename from minecode/tests/discovery/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom rename to discovery/tests/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom diff --git a/minecode/tests/discovery/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom_search_expected.json b/discovery/tests/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom_search_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom_search_expected.json rename to discovery/tests/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom_search_expected.json diff --git a/minecode/tests/discovery/testfiles/housekeeping/declared_license_search_expected.json b/discovery/tests/testfiles/housekeeping/declared_license_search_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/housekeeping/declared_license_search_expected.json rename to discovery/tests/testfiles/housekeeping/declared_license_search_expected.json diff --git a/minecode/tests/discovery/testfiles/housekeeping/example_expected.json b/discovery/tests/testfiles/housekeeping/example_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/housekeeping/example_expected.json rename to discovery/tests/testfiles/housekeeping/example_expected.json diff --git a/minecode/tests/discovery/testfiles/housekeeping/ignore_upper_case_search_expected.json b/discovery/tests/testfiles/housekeeping/ignore_upper_case_search_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/housekeeping/ignore_upper_case_search_expected.json rename to discovery/tests/testfiles/housekeeping/ignore_upper_case_search_expected.json diff --git a/minecode/tests/discovery/testfiles/housekeeping/license_expression_search_expected.json b/discovery/tests/testfiles/housekeeping/license_expression_search_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/housekeeping/license_expression_search_expected.json rename to discovery/tests/testfiles/housekeeping/license_expression_search_expected.json diff --git a/minecode/tests/discovery/testfiles/maven/end2end/bytejta-supports-0.5.0-ALPHA4.pom b/discovery/tests/testfiles/maven/end2end/bytejta-supports-0.5.0-ALPHA4.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end/bytejta-supports-0.5.0-ALPHA4.pom rename to discovery/tests/testfiles/maven/end2end/bytejta-supports-0.5.0-ALPHA4.pom diff --git a/minecode/tests/discovery/testfiles/maven/end2end/expected_mapped_packages.json b/discovery/tests/testfiles/maven/end2end/expected_mapped_packages.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end/expected_mapped_packages.json rename to discovery/tests/testfiles/maven/end2end/expected_mapped_packages.json diff --git a/minecode/tests/discovery/testfiles/maven/end2end/expected_visited_uris.json b/discovery/tests/testfiles/maven/end2end/expected_visited_uris.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end/expected_visited_uris.json rename to discovery/tests/testfiles/maven/end2end/expected_visited_uris.json diff --git a/minecode/tests/discovery/testfiles/maven/end2end/test_uris.json b/discovery/tests/testfiles/maven/end2end/test_uris.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end/test_uris.json rename to discovery/tests/testfiles/maven/end2end/test_uris.json diff --git a/minecode/tests/discovery/testfiles/maven/end2end_index/expected_visited_increment_index.json b/discovery/tests/testfiles/maven/end2end_index/expected_visited_increment_index.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end_index/expected_visited_increment_index.json rename to discovery/tests/testfiles/maven/end2end_index/expected_visited_increment_index.json diff --git a/minecode/tests/discovery/testfiles/maven/end2end_index/expected_visited_index.json b/discovery/tests/testfiles/maven/end2end_index/expected_visited_index.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end_index/expected_visited_index.json rename to discovery/tests/testfiles/maven/end2end_index/expected_visited_index.json diff --git a/minecode/tests/discovery/testfiles/maven/end2end_index/nexus-maven-repository-index.163.gz b/discovery/tests/testfiles/maven/end2end_index/nexus-maven-repository-index.163.gz similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end_index/nexus-maven-repository-index.163.gz rename to discovery/tests/testfiles/maven/end2end_index/nexus-maven-repository-index.163.gz diff --git a/minecode/tests/discovery/testfiles/maven/end2end_index/nexus-maven-repository-index.properties b/discovery/tests/testfiles/maven/end2end_index/nexus-maven-repository-index.properties similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end_index/nexus-maven-repository-index.properties rename to discovery/tests/testfiles/maven/end2end_index/nexus-maven-repository-index.properties diff --git a/minecode/tests/discovery/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-index-data.json b/discovery/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-index-data.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-index-data.json rename to discovery/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-index-data.json diff --git a/minecode/tests/discovery/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-pom-data.json b/discovery/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-pom-data.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-pom-data.json rename to discovery/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-pom-data.json diff --git a/minecode/tests/discovery/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21.pom b/discovery/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21.pom rename to discovery/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21.pom diff --git a/minecode/tests/discovery/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-index.json b/discovery/tests/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-index.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-index.json rename to discovery/tests/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-index.json diff --git a/minecode/tests/discovery/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-pom.json b/discovery/tests/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-pom.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-pom.json rename to discovery/tests/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-pom.json diff --git a/minecode/tests/discovery/testfiles/maven/end2end_unicode/commons-jaxrs-1.22.pom b/discovery/tests/testfiles/maven/end2end_unicode/commons-jaxrs-1.22.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end_unicode/commons-jaxrs-1.22.pom rename to discovery/tests/testfiles/maven/end2end_unicode/commons-jaxrs-1.22.pom diff --git a/minecode/tests/discovery/testfiles/maven/end2end_unicode/expected_mapped_commons-jaxrs-1.22.json b/discovery/tests/testfiles/maven/end2end_unicode/expected_mapped_commons-jaxrs-1.22.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end_unicode/expected_mapped_commons-jaxrs-1.22.json rename to discovery/tests/testfiles/maven/end2end_unicode/expected_mapped_commons-jaxrs-1.22.json diff --git a/minecode/tests/discovery/testfiles/maven/end2end_unicode/expected_visited_commons-jaxrs-1.22.json b/discovery/tests/testfiles/maven/end2end_unicode/expected_visited_commons-jaxrs-1.22.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/end2end_unicode/expected_visited_commons-jaxrs-1.22.json rename to discovery/tests/testfiles/maven/end2end_unicode/expected_visited_commons-jaxrs-1.22.json diff --git a/minecode/tests/discovery/testfiles/maven/html/app.html b/discovery/tests/testfiles/maven/html/app.html similarity index 100% rename from minecode/tests/discovery/testfiles/maven/html/app.html rename to discovery/tests/testfiles/maven/html/app.html diff --git a/minecode/tests/discovery/testfiles/maven/html/jcenter.bintray.com.html b/discovery/tests/testfiles/maven/html/jcenter.bintray.com.html similarity index 100% rename from minecode/tests/discovery/testfiles/maven/html/jcenter.bintray.com.html rename to discovery/tests/testfiles/maven/html/jcenter.bintray.com.html diff --git a/minecode/tests/discovery/testfiles/maven/html/stateframework-compiler.html b/discovery/tests/testfiles/maven/html/stateframework-compiler.html similarity index 100% rename from minecode/tests/discovery/testfiles/maven/html/stateframework-compiler.html rename to discovery/tests/testfiles/maven/html/stateframework-compiler.html diff --git a/minecode/tests/discovery/testfiles/maven/html/visitor_expected_app.html.json b/discovery/tests/testfiles/maven/html/visitor_expected_app.html.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/html/visitor_expected_app.html.json rename to discovery/tests/testfiles/maven/html/visitor_expected_app.html.json diff --git a/minecode/tests/discovery/testfiles/maven/html/visitor_expected_jcenter.bintray.com2.html.json b/discovery/tests/testfiles/maven/html/visitor_expected_jcenter.bintray.com2.html.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/html/visitor_expected_jcenter.bintray.com2.html.json rename to discovery/tests/testfiles/maven/html/visitor_expected_jcenter.bintray.com2.html.json diff --git a/minecode/tests/discovery/testfiles/maven/html/visitor_expected_stateframework-compiler.html.json b/discovery/tests/testfiles/maven/html/visitor_expected_stateframework-compiler.html.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/html/visitor_expected_stateframework-compiler.html.json rename to discovery/tests/testfiles/maven/html/visitor_expected_stateframework-compiler.html.json diff --git a/minecode/tests/discovery/testfiles/maven/index/buggy/expected_artifacts-defaults.json b/discovery/tests/testfiles/maven/index/buggy/expected_artifacts-defaults.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/buggy/expected_artifacts-defaults.json rename to discovery/tests/testfiles/maven/index/buggy/expected_artifacts-defaults.json diff --git a/minecode/tests/discovery/testfiles/maven/index/buggy/expected_artifacts.json b/discovery/tests/testfiles/maven/index/buggy/expected_artifacts.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/buggy/expected_artifacts.json rename to discovery/tests/testfiles/maven/index/buggy/expected_artifacts.json diff --git a/minecode/tests/discovery/testfiles/maven/index/buggy/expected_entries.json b/discovery/tests/testfiles/maven/index/buggy/expected_entries.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/buggy/expected_entries.json rename to discovery/tests/testfiles/maven/index/buggy/expected_entries.json diff --git a/minecode/tests/discovery/testfiles/maven/index/buggy/expected_uris.json b/discovery/tests/testfiles/maven/index/buggy/expected_uris.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/buggy/expected_uris.json rename to discovery/tests/testfiles/maven/index/buggy/expected_uris.json diff --git a/minecode/tests/discovery/testfiles/maven/index/buggy/expected_visited_uris.json b/discovery/tests/testfiles/maven/index/buggy/expected_visited_uris.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/buggy/expected_visited_uris.json rename to discovery/tests/testfiles/maven/index/buggy/expected_visited_uris.json diff --git a/minecode/tests/discovery/testfiles/maven/index/buggy/nexus-maven-repository-index.gz b/discovery/tests/testfiles/maven/index/buggy/nexus-maven-repository-index.gz similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/buggy/nexus-maven-repository-index.gz rename to discovery/tests/testfiles/maven/index/buggy/nexus-maven-repository-index.gz diff --git a/minecode/tests/discovery/testfiles/maven/index/expected_artifacts-all-worthy.json b/discovery/tests/testfiles/maven/index/expected_artifacts-all-worthy.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/expected_artifacts-all-worthy.json rename to discovery/tests/testfiles/maven/index/expected_artifacts-all-worthy.json diff --git a/minecode/tests/discovery/testfiles/maven/index/expected_artifacts-defaults.json b/discovery/tests/testfiles/maven/index/expected_artifacts-defaults.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/expected_artifacts-defaults.json rename to discovery/tests/testfiles/maven/index/expected_artifacts-defaults.json diff --git a/minecode/tests/discovery/testfiles/maven/index/expected_artifacts.json b/discovery/tests/testfiles/maven/index/expected_artifacts.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/expected_artifacts.json rename to discovery/tests/testfiles/maven/index/expected_artifacts.json diff --git a/minecode/tests/discovery/testfiles/maven/index/expected_entries.json b/discovery/tests/testfiles/maven/index/expected_entries.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/expected_entries.json rename to discovery/tests/testfiles/maven/index/expected_entries.json diff --git a/minecode/tests/discovery/testfiles/maven/index/expected_uris.json b/discovery/tests/testfiles/maven/index/expected_uris.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/expected_uris.json rename to discovery/tests/testfiles/maven/index/expected_uris.json diff --git a/minecode/tests/discovery/testfiles/maven/index/increment/expected_artifacts-defaults.json b/discovery/tests/testfiles/maven/index/increment/expected_artifacts-defaults.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/increment/expected_artifacts-defaults.json rename to discovery/tests/testfiles/maven/index/increment/expected_artifacts-defaults.json diff --git a/minecode/tests/discovery/testfiles/maven/index/increment/expected_artifacts.json b/discovery/tests/testfiles/maven/index/increment/expected_artifacts.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/increment/expected_artifacts.json rename to discovery/tests/testfiles/maven/index/increment/expected_artifacts.json diff --git a/minecode/tests/discovery/testfiles/maven/index/increment/expected_entries.json b/discovery/tests/testfiles/maven/index/increment/expected_entries.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/increment/expected_entries.json rename to discovery/tests/testfiles/maven/index/increment/expected_entries.json diff --git a/minecode/tests/discovery/testfiles/maven/index/increment/expected_properties_uris.json b/discovery/tests/testfiles/maven/index/increment/expected_properties_uris.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/increment/expected_properties_uris.json rename to discovery/tests/testfiles/maven/index/increment/expected_properties_uris.json diff --git a/minecode/tests/discovery/testfiles/maven/index/increment/expected_uris.json b/discovery/tests/testfiles/maven/index/increment/expected_uris.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/increment/expected_uris.json rename to discovery/tests/testfiles/maven/index/increment/expected_uris.json diff --git a/minecode/tests/discovery/testfiles/maven/index/increment/nexus-maven-repository-index.445.gz b/discovery/tests/testfiles/maven/index/increment/nexus-maven-repository-index.445.gz similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/increment/nexus-maven-repository-index.445.gz rename to discovery/tests/testfiles/maven/index/increment/nexus-maven-repository-index.445.gz diff --git a/minecode/tests/discovery/testfiles/maven/index/increment/nexus-maven-repository-index.properties b/discovery/tests/testfiles/maven/index/increment/nexus-maven-repository-index.properties similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/increment/nexus-maven-repository-index.properties rename to discovery/tests/testfiles/maven/index/increment/nexus-maven-repository-index.properties diff --git a/minecode/tests/discovery/testfiles/maven/index/increment2/expected_mini_package.json b/discovery/tests/testfiles/maven/index/increment2/expected_mini_package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/increment2/expected_mini_package.json rename to discovery/tests/testfiles/maven/index/increment2/expected_mini_package.json diff --git a/minecode/tests/discovery/testfiles/maven/index/increment2/expected_uris.json b/discovery/tests/testfiles/maven/index/increment2/expected_uris.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/increment2/expected_uris.json rename to discovery/tests/testfiles/maven/index/increment2/expected_uris.json diff --git a/minecode/tests/discovery/testfiles/maven/index/increment2/nexus-maven-repository-index.457.gz b/discovery/tests/testfiles/maven/index/increment2/nexus-maven-repository-index.457.gz similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/increment2/nexus-maven-repository-index.457.gz rename to discovery/tests/testfiles/maven/index/increment2/nexus-maven-repository-index.457.gz diff --git a/minecode/tests/discovery/testfiles/maven/index/nexus-maven-repository-index.gz b/discovery/tests/testfiles/maven/index/nexus-maven-repository-index.gz similarity index 100% rename from minecode/tests/discovery/testfiles/maven/index/nexus-maven-repository-index.gz rename to discovery/tests/testfiles/maven/index/nexus-maven-repository-index.gz diff --git a/minecode/tests/discovery/testfiles/maven/mapper/ant-1.6.5.pom b/discovery/tests/testfiles/maven/mapper/ant-1.6.5.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/ant-1.6.5.pom rename to discovery/tests/testfiles/maven/mapper/ant-1.6.5.pom diff --git a/minecode/tests/discovery/testfiles/maven/mapper/ant-1.6.5.pom.json b/discovery/tests/testfiles/maven/mapper/ant-1.6.5.pom.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/ant-1.6.5.pom.json rename to discovery/tests/testfiles/maven/mapper/ant-1.6.5.pom.json diff --git a/minecode/tests/discovery/testfiles/maven/mapper/axis-1.4.pom b/discovery/tests/testfiles/maven/mapper/axis-1.4.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/axis-1.4.pom rename to discovery/tests/testfiles/maven/mapper/axis-1.4.pom diff --git a/minecode/tests/discovery/testfiles/maven/mapper/axis-1.4.pom.package.json b/discovery/tests/testfiles/maven/mapper/axis-1.4.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/axis-1.4.pom.package.json rename to discovery/tests/testfiles/maven/mapper/axis-1.4.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/mapper/commons-jaxrs-1.21.pom b/discovery/tests/testfiles/maven/mapper/commons-jaxrs-1.21.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/commons-jaxrs-1.21.pom rename to discovery/tests/testfiles/maven/mapper/commons-jaxrs-1.21.pom diff --git a/minecode/tests/discovery/testfiles/maven/mapper/commons-jaxrs-1.21.pom.package.json b/discovery/tests/testfiles/maven/mapper/commons-jaxrs-1.21.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/commons-jaxrs-1.21.pom.package.json rename to discovery/tests/testfiles/maven/mapper/commons-jaxrs-1.21.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/mapper/commons-pool-1.5.7.pom b/discovery/tests/testfiles/maven/mapper/commons-pool-1.5.7.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/commons-pool-1.5.7.pom rename to discovery/tests/testfiles/maven/mapper/commons-pool-1.5.7.pom diff --git a/minecode/tests/discovery/testfiles/maven/mapper/commons-pool-1.5.7.pom.package.json b/discovery/tests/testfiles/maven/mapper/commons-pool-1.5.7.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/commons-pool-1.5.7.pom.package.json rename to discovery/tests/testfiles/maven/mapper/commons-pool-1.5.7.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/mapper/depgraph-view-0.1.pom b/discovery/tests/testfiles/maven/mapper/depgraph-view-0.1.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/depgraph-view-0.1.pom rename to discovery/tests/testfiles/maven/mapper/depgraph-view-0.1.pom diff --git a/minecode/tests/discovery/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom b/discovery/tests/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom rename to discovery/tests/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom diff --git a/minecode/tests/discovery/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom.package.json b/discovery/tests/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom.package.json rename to discovery/tests/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom b/discovery/tests/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom rename to discovery/tests/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom diff --git a/minecode/tests/discovery/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom.package.json b/discovery/tests/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom.package.json rename to discovery/tests/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/mapper/struts-menu-2.4.2.pom b/discovery/tests/testfiles/maven/mapper/struts-menu-2.4.2.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/struts-menu-2.4.2.pom rename to discovery/tests/testfiles/maven/mapper/struts-menu-2.4.2.pom diff --git a/minecode/tests/discovery/testfiles/maven/mapper/struts-menu-2.4.2.pom.package.json b/discovery/tests/testfiles/maven/mapper/struts-menu-2.4.2.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/struts-menu-2.4.2.pom.package.json rename to discovery/tests/testfiles/maven/mapper/struts-menu-2.4.2.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/mapper/xbean-jmx-2.0.pom b/discovery/tests/testfiles/maven/mapper/xbean-jmx-2.0.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/xbean-jmx-2.0.pom rename to discovery/tests/testfiles/maven/mapper/xbean-jmx-2.0.pom diff --git a/minecode/tests/discovery/testfiles/maven/mapper/xbean-jmx-2.0.pom.package.json b/discovery/tests/testfiles/maven/mapper/xbean-jmx-2.0.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/mapper/xbean-jmx-2.0.pom.package.json rename to discovery/tests/testfiles/maven/mapper/xbean-jmx-2.0.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/maven-metadata/expected_maven_xml.json b/discovery/tests/testfiles/maven/maven-metadata/expected_maven_xml.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/maven-metadata/expected_maven_xml.json rename to discovery/tests/testfiles/maven/maven-metadata/expected_maven_xml.json diff --git a/minecode/tests/discovery/testfiles/maven/maven-metadata/maven-metadata.xml b/discovery/tests/testfiles/maven/maven-metadata/maven-metadata.xml similarity index 100% rename from minecode/tests/discovery/testfiles/maven/maven-metadata/maven-metadata.xml rename to discovery/tests/testfiles/maven/maven-metadata/maven-metadata.xml diff --git a/minecode/tests/discovery/testfiles/maven/parsing/empty/common-object-1.0.2.pom b/discovery/tests/testfiles/maven/parsing/empty/common-object-1.0.2.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/empty/common-object-1.0.2.pom rename to discovery/tests/testfiles/maven/parsing/empty/common-object-1.0.2.pom diff --git a/minecode/tests/discovery/testfiles/maven/parsing/empty/common-object-1.0.2.pom.package.json b/discovery/tests/testfiles/maven/parsing/empty/common-object-1.0.2.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/empty/common-object-1.0.2.pom.package.json rename to discovery/tests/testfiles/maven/parsing/empty/common-object-1.0.2.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom b/discovery/tests/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom rename to discovery/tests/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom diff --git a/minecode/tests/discovery/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom.package.json b/discovery/tests/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom.package.json rename to discovery/tests/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom b/discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom rename to discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom diff --git a/minecode/tests/discovery/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom.package.json b/discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom.package.json rename to discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom b/discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom rename to discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom diff --git a/minecode/tests/discovery/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom.package.json b/discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom.package.json rename to discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom b/discovery/tests/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom rename to discovery/tests/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom diff --git a/minecode/tests/discovery/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom.package.json b/discovery/tests/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom.package.json rename to discovery/tests/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom b/discovery/tests/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom rename to discovery/tests/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom diff --git a/minecode/tests/discovery/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom.package.json b/discovery/tests/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom.package.json rename to discovery/tests/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom b/discovery/tests/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom rename to discovery/tests/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom diff --git a/minecode/tests/discovery/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom.package.json b/discovery/tests/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom.package.json rename to discovery/tests/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom b/discovery/tests/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom rename to discovery/tests/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom diff --git a/minecode/tests/discovery/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom.package.json b/discovery/tests/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom.package.json rename to discovery/tests/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom b/discovery/tests/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom rename to discovery/tests/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom diff --git a/minecode/tests/discovery/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom.package.json b/discovery/tests/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom.package.json rename to discovery/tests/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/parsing/parse/jds-2.17.0718b.pom b/discovery/tests/testfiles/maven/parsing/parse/jds-2.17.0718b.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/parse/jds-2.17.0718b.pom rename to discovery/tests/testfiles/maven/parsing/parse/jds-2.17.0718b.pom diff --git a/minecode/tests/discovery/testfiles/maven/parsing/parse/jds-2.17.0718b.pom.package.json b/discovery/tests/testfiles/maven/parsing/parse/jds-2.17.0718b.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/parse/jds-2.17.0718b.pom.package.json rename to discovery/tests/testfiles/maven/parsing/parse/jds-2.17.0718b.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/parsing/parse/jds-3.0.1.pom b/discovery/tests/testfiles/maven/parsing/parse/jds-3.0.1.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/parse/jds-3.0.1.pom rename to discovery/tests/testfiles/maven/parsing/parse/jds-3.0.1.pom diff --git a/minecode/tests/discovery/testfiles/maven/parsing/parse/jds-3.0.1.pom.package.json b/discovery/tests/testfiles/maven/parsing/parse/jds-3.0.1.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/parse/jds-3.0.1.pom.package.json rename to discovery/tests/testfiles/maven/parsing/parse/jds-3.0.1.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom b/discovery/tests/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom rename to discovery/tests/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom diff --git a/minecode/tests/discovery/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom.package.json b/discovery/tests/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom.package.json rename to discovery/tests/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom b/discovery/tests/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom rename to discovery/tests/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom diff --git a/minecode/tests/discovery/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom.package.json b/discovery/tests/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom.package.json rename to discovery/tests/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom.package.json diff --git a/minecode/tests/discovery/testfiles/maven/pom/classworlds-1.1-alpha-2.pom b/discovery/tests/testfiles/maven/pom/classworlds-1.1-alpha-2.pom similarity index 100% rename from minecode/tests/discovery/testfiles/maven/pom/classworlds-1.1-alpha-2.pom rename to discovery/tests/testfiles/maven/pom/classworlds-1.1-alpha-2.pom diff --git a/minecode/tests/discovery/testfiles/npm/0flux.json b/discovery/tests/testfiles/npm/0flux.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/0flux.json rename to discovery/tests/testfiles/npm/0flux.json diff --git a/minecode/tests/discovery/testfiles/npm/0flux_npm_expected.json b/discovery/tests/testfiles/npm/0flux_npm_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/0flux_npm_expected.json rename to discovery/tests/testfiles/npm/0flux_npm_expected.json diff --git a/minecode/tests/discovery/testfiles/npm/1000_records.json b/discovery/tests/testfiles/npm/1000_records.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/1000_records.json rename to discovery/tests/testfiles/npm/1000_records.json diff --git a/minecode/tests/discovery/testfiles/npm/2112.json b/discovery/tests/testfiles/npm/2112.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/2112.json rename to discovery/tests/testfiles/npm/2112.json diff --git a/minecode/tests/discovery/testfiles/npm/29_record_expected.json b/discovery/tests/testfiles/npm/29_record_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/29_record_expected.json rename to discovery/tests/testfiles/npm/29_record_expected.json diff --git a/minecode/tests/discovery/testfiles/npm/554_record_expected.json b/discovery/tests/testfiles/npm/554_record_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/554_record_expected.json rename to discovery/tests/testfiles/npm/554_record_expected.json diff --git a/minecode/tests/discovery/testfiles/npm/expected_1000_records.json b/discovery/tests/testfiles/npm/expected_1000_records.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/expected_1000_records.json rename to discovery/tests/testfiles/npm/expected_1000_records.json diff --git a/minecode/tests/discovery/testfiles/npm/expected_doclimit_visitor.json b/discovery/tests/testfiles/npm/expected_doclimit_visitor.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/expected_doclimit_visitor.json rename to discovery/tests/testfiles/npm/expected_doclimit_visitor.json diff --git a/minecode/tests/discovery/testfiles/npm/expected_npmdownload_data_vistor.json b/discovery/tests/testfiles/npm/expected_npmdownload_data_vistor.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/expected_npmdownload_data_vistor.json rename to discovery/tests/testfiles/npm/expected_npmdownload_data_vistor.json diff --git a/minecode/tests/discovery/testfiles/npm/expected_npmdownloadvistor.json b/discovery/tests/testfiles/npm/expected_npmdownloadvistor.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/expected_npmdownloadvistor.json rename to discovery/tests/testfiles/npm/expected_npmdownloadvistor.json diff --git a/minecode/tests/discovery/testfiles/npm/expected_npmindexvisitor.json b/discovery/tests/testfiles/npm/expected_npmindexvisitor.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/expected_npmindexvisitor.json rename to discovery/tests/testfiles/npm/expected_npmindexvisitor.json diff --git a/minecode/tests/discovery/testfiles/npm/expected_over_limit.json b/discovery/tests/testfiles/npm/expected_over_limit.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/expected_over_limit.json rename to discovery/tests/testfiles/npm/expected_over_limit.json diff --git a/minecode/tests/discovery/testfiles/npm/expected_ticket_439.json b/discovery/tests/testfiles/npm/expected_ticket_439.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/expected_ticket_439.json rename to discovery/tests/testfiles/npm/expected_ticket_439.json diff --git a/minecode/tests/discovery/testfiles/npm/expected_ticket_440.json b/discovery/tests/testfiles/npm/expected_ticket_440.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/expected_ticket_440.json rename to discovery/tests/testfiles/npm/expected_ticket_440.json diff --git a/minecode/tests/discovery/testfiles/npm/grunticon-sass.json b/discovery/tests/testfiles/npm/grunticon-sass.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/grunticon-sass.json rename to discovery/tests/testfiles/npm/grunticon-sass.json diff --git a/minecode/tests/discovery/testfiles/npm/jsonp-filter-expected.json b/discovery/tests/testfiles/npm/jsonp-filter-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/jsonp-filter-expected.json rename to discovery/tests/testfiles/npm/jsonp-filter-expected.json diff --git a/minecode/tests/discovery/testfiles/npm/jsonp-filter.json b/discovery/tests/testfiles/npm/jsonp-filter.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/jsonp-filter.json rename to discovery/tests/testfiles/npm/jsonp-filter.json diff --git a/minecode/tests/discovery/testfiles/npm/mapper/index.expected.json b/discovery/tests/testfiles/npm/mapper/index.expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/mapper/index.expected.json rename to discovery/tests/testfiles/npm/mapper/index.expected.json diff --git a/minecode/tests/discovery/testfiles/npm/mapper/index.json b/discovery/tests/testfiles/npm/mapper/index.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/mapper/index.json rename to discovery/tests/testfiles/npm/mapper/index.json diff --git a/minecode/tests/discovery/testfiles/npm/microdata-node_expected.json b/discovery/tests/testfiles/npm/microdata-node_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/microdata-node_expected.json rename to discovery/tests/testfiles/npm/microdata-node_expected.json diff --git a/minecode/tests/discovery/testfiles/npm/microdata.json b/discovery/tests/testfiles/npm/microdata.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/microdata.json rename to discovery/tests/testfiles/npm/microdata.json diff --git a/minecode/tests/discovery/testfiles/npm/npm_2112_expected.json b/discovery/tests/testfiles/npm/npm_2112_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/npm_2112_expected.json rename to discovery/tests/testfiles/npm/npm_2112_expected.json diff --git a/minecode/tests/discovery/testfiles/npm/over_limit.json b/discovery/tests/testfiles/npm/over_limit.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/over_limit.json rename to discovery/tests/testfiles/npm/over_limit.json diff --git a/minecode/tests/discovery/testfiles/npm/replicate_doc1.json b/discovery/tests/testfiles/npm/replicate_doc1.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/replicate_doc1.json rename to discovery/tests/testfiles/npm/replicate_doc1.json diff --git a/minecode/tests/discovery/testfiles/npm/ticket_439.json b/discovery/tests/testfiles/npm/ticket_439.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/ticket_439.json rename to discovery/tests/testfiles/npm/ticket_439.json diff --git a/minecode/tests/discovery/testfiles/npm/ticket_440_records.json b/discovery/tests/testfiles/npm/ticket_440_records.json similarity index 100% rename from minecode/tests/discovery/testfiles/npm/ticket_440_records.json rename to discovery/tests/testfiles/npm/ticket_440_records.json diff --git a/minecode/tests/discovery/testfiles/pypi/boolean.py-2.0.dev3.json b/discovery/tests/testfiles/pypi/boolean.py-2.0.dev3.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/boolean.py-2.0.dev3.json rename to discovery/tests/testfiles/pypi/boolean.py-2.0.dev3.json diff --git a/minecode/tests/discovery/testfiles/pypi/boolean.py.json b/discovery/tests/testfiles/pypi/boolean.py.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/boolean.py.json rename to discovery/tests/testfiles/pypi/boolean.py.json diff --git a/minecode/tests/discovery/testfiles/pypi/cage.json b/discovery/tests/testfiles/pypi/cage.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/cage.json rename to discovery/tests/testfiles/pypi/cage.json diff --git a/minecode/tests/discovery/testfiles/pypi/cage_1.1.2.json b/discovery/tests/testfiles/pypi/cage_1.1.2.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/cage_1.1.2.json rename to discovery/tests/testfiles/pypi/cage_1.1.2.json diff --git a/minecode/tests/discovery/testfiles/pypi/cage_1.1.3.json b/discovery/tests/testfiles/pypi/cage_1.1.3.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/cage_1.1.3.json rename to discovery/tests/testfiles/pypi/cage_1.1.3.json diff --git a/minecode/tests/discovery/testfiles/pypi/expected-CAGE-1.1.2.json b/discovery/tests/testfiles/pypi/expected-CAGE-1.1.2.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/expected-CAGE-1.1.2.json rename to discovery/tests/testfiles/pypi/expected-CAGE-1.1.2.json diff --git a/minecode/tests/discovery/testfiles/pypi/expected-CAGE-1.1.3.json b/discovery/tests/testfiles/pypi/expected-CAGE-1.1.3.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/expected-CAGE-1.1.3.json rename to discovery/tests/testfiles/pypi/expected-CAGE-1.1.3.json diff --git a/minecode/tests/discovery/testfiles/pypi/expected-boolean.py-2.0.dev3.json b/discovery/tests/testfiles/pypi/expected-boolean.py-2.0.dev3.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/expected-boolean.py-2.0.dev3.json rename to discovery/tests/testfiles/pypi/expected-boolean.py-2.0.dev3.json diff --git a/minecode/tests/discovery/testfiles/pypi/expected-lxml-3.2.0.json b/discovery/tests/testfiles/pypi/expected-lxml-3.2.0.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/expected-lxml-3.2.0.json rename to discovery/tests/testfiles/pypi/expected-lxml-3.2.0.json diff --git a/minecode/tests/discovery/testfiles/pypi/expected_data-boolean.py-2.0.dev3.json b/discovery/tests/testfiles/pypi/expected_data-boolean.py-2.0.dev3.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/expected_data-boolean.py-2.0.dev3.json rename to discovery/tests/testfiles/pypi/expected_data-boolean.py-2.0.dev3.json diff --git a/minecode/tests/discovery/testfiles/pypi/expected_data-cage_1.1.2.json b/discovery/tests/testfiles/pypi/expected_data-cage_1.1.2.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/expected_data-cage_1.1.2.json rename to discovery/tests/testfiles/pypi/expected_data-cage_1.1.2.json diff --git a/minecode/tests/discovery/testfiles/pypi/expected_data-cage_1.1.3.json b/discovery/tests/testfiles/pypi/expected_data-cage_1.1.3.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/expected_data-cage_1.1.3.json rename to discovery/tests/testfiles/pypi/expected_data-cage_1.1.3.json diff --git a/minecode/tests/discovery/testfiles/pypi/expected_uri_visitor1.json b/discovery/tests/testfiles/pypi/expected_uri_visitor1.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/expected_uri_visitor1.json rename to discovery/tests/testfiles/pypi/expected_uri_visitor1.json diff --git a/minecode/tests/discovery/testfiles/pypi/expected_uri_visitor2.json b/discovery/tests/testfiles/pypi/expected_uri_visitor2.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/expected_uri_visitor2.json rename to discovery/tests/testfiles/pypi/expected_uri_visitor2.json diff --git a/minecode/tests/discovery/testfiles/pypi/expected_uris-boolean.py-2.0.dev3.json b/discovery/tests/testfiles/pypi/expected_uris-boolean.py-2.0.dev3.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/expected_uris-boolean.py-2.0.dev3.json rename to discovery/tests/testfiles/pypi/expected_uris-boolean.py-2.0.dev3.json diff --git a/minecode/tests/discovery/testfiles/pypi/expected_uris-boolean.py.json b/discovery/tests/testfiles/pypi/expected_uris-boolean.py.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/expected_uris-boolean.py.json rename to discovery/tests/testfiles/pypi/expected_uris-boolean.py.json diff --git a/minecode/tests/discovery/testfiles/pypi/expected_uris-cage.json b/discovery/tests/testfiles/pypi/expected_uris-cage.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/expected_uris-cage.json rename to discovery/tests/testfiles/pypi/expected_uris-cage.json diff --git a/minecode/tests/discovery/testfiles/pypi/expected_uris-cage_1.1.2.json b/discovery/tests/testfiles/pypi/expected_uris-cage_1.1.2.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/expected_uris-cage_1.1.2.json rename to discovery/tests/testfiles/pypi/expected_uris-cage_1.1.2.json diff --git a/minecode/tests/discovery/testfiles/pypi/expected_uris-cage_1.1.3.json b/discovery/tests/testfiles/pypi/expected_uris-cage_1.1.3.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/expected_uris-cage_1.1.3.json rename to discovery/tests/testfiles/pypi/expected_uris-cage_1.1.3.json diff --git a/minecode/tests/discovery/testfiles/pypi/lxml-3.2.0.json b/discovery/tests/testfiles/pypi/lxml-3.2.0.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/lxml-3.2.0.json rename to discovery/tests/testfiles/pypi/lxml-3.2.0.json diff --git a/minecode/tests/discovery/testfiles/pypi/map/3to2-1.1.1.json b/discovery/tests/testfiles/pypi/map/3to2-1.1.1.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/map/3to2-1.1.1.json rename to discovery/tests/testfiles/pypi/map/3to2-1.1.1.json diff --git a/minecode/tests/discovery/testfiles/pypi/map/expected-3to2-1.1.1.json b/discovery/tests/testfiles/pypi/map/expected-3to2-1.1.1.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/map/expected-3to2-1.1.1.json rename to discovery/tests/testfiles/pypi/map/expected-3to2-1.1.1.json diff --git a/minecode/tests/discovery/testfiles/pypi/pypiindexvisitor-expected.json b/discovery/tests/testfiles/pypi/pypiindexvisitor-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/pypi/pypiindexvisitor-expected.json rename to discovery/tests/testfiles/pypi/pypiindexvisitor-expected.json diff --git a/minecode/tests/discovery/testfiles/rsync/rsync_dev.dir b/discovery/tests/testfiles/rsync/rsync_dev.dir similarity index 100% rename from minecode/tests/discovery/testfiles/rsync/rsync_dev.dir rename to discovery/tests/testfiles/rsync/rsync_dev.dir diff --git a/minecode/tests/discovery/testfiles/rsync/rsync_dir/bar/that/baz b/discovery/tests/testfiles/rsync/rsync_dir/bar/that/baz similarity index 100% rename from minecode/tests/discovery/testfiles/rsync/rsync_dir/bar/that/baz rename to discovery/tests/testfiles/rsync/rsync_dir/bar/that/baz diff --git a/minecode/tests/discovery/testfiles/rsync/rsync_dir/bar/this b/discovery/tests/testfiles/rsync/rsync_dir/bar/this similarity index 100% rename from minecode/tests/discovery/testfiles/rsync/rsync_dir/bar/this rename to discovery/tests/testfiles/rsync/rsync_dir/bar/this diff --git a/minecode/tests/discovery/testfiles/rsync/rsync_dir/foo b/discovery/tests/testfiles/rsync/rsync_dir/foo similarity index 100% rename from minecode/tests/discovery/testfiles/rsync/rsync_dir/foo rename to discovery/tests/testfiles/rsync/rsync_dir/foo diff --git a/minecode/tests/discovery/testfiles/rsync/rsync_modules b/discovery/tests/testfiles/rsync/rsync_modules similarity index 100% rename from minecode/tests/discovery/testfiles/rsync/rsync_modules rename to discovery/tests/testfiles/rsync/rsync_modules diff --git a/minecode/tests/discovery/testfiles/rsync/rsync_v3.0.9_protocol30.dir b/discovery/tests/testfiles/rsync/rsync_v3.0.9_protocol30.dir similarity index 100% rename from minecode/tests/discovery/testfiles/rsync/rsync_v3.0.9_protocol30.dir rename to discovery/tests/testfiles/rsync/rsync_v3.0.9_protocol30.dir diff --git a/minecode/tests/discovery/testfiles/rsync/rsync_v3.1.0_protocol31.dir b/discovery/tests/testfiles/rsync/rsync_v3.1.0_protocol31.dir similarity index 100% rename from minecode/tests/discovery/testfiles/rsync/rsync_v3.1.0_protocol31.dir rename to discovery/tests/testfiles/rsync/rsync_v3.1.0_protocol31.dir diff --git a/minecode/tests/discovery/testfiles/rsync/rsync_wicket.dir b/discovery/tests/testfiles/rsync/rsync_wicket.dir similarity index 100% rename from minecode/tests/discovery/testfiles/rsync/rsync_wicket.dir rename to discovery/tests/testfiles/rsync/rsync_wicket.dir diff --git a/minecode/tests/discovery/testfiles/rubygems/0mq-0.4.1.gem.metadata b/discovery/tests/testfiles/rubygems/0mq-0.4.1.gem.metadata similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/0mq-0.4.1.gem.metadata rename to discovery/tests/testfiles/rubygems/0mq-0.4.1.gem.metadata diff --git a/minecode/tests/discovery/testfiles/rubygems/0mq-0.4.1.gem.package.json b/discovery/tests/testfiles/rubygems/0mq-0.4.1.gem.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/0mq-0.4.1.gem.package.json rename to discovery/tests/testfiles/rubygems/0mq-0.4.1.gem.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/a_okay-0.1.0.gem b/discovery/tests/testfiles/rubygems/a_okay-0.1.0.gem similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/a_okay-0.1.0.gem rename to discovery/tests/testfiles/rubygems/a_okay-0.1.0.gem diff --git a/minecode/tests/discovery/testfiles/rubygems/a_okay-0.1.0.gem.metadata b/discovery/tests/testfiles/rubygems/a_okay-0.1.0.gem.metadata similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/a_okay-0.1.0.gem.metadata rename to discovery/tests/testfiles/rubygems/a_okay-0.1.0.gem.metadata diff --git a/minecode/tests/discovery/testfiles/rubygems/a_okay-0.1.0.gem.package.json b/discovery/tests/testfiles/rubygems/a_okay-0.1.0.gem.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/a_okay-0.1.0.gem.package.json rename to discovery/tests/testfiles/rubygems/a_okay-0.1.0.gem.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/action_tracker-1.0.2.gem b/discovery/tests/testfiles/rubygems/action_tracker-1.0.2.gem similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/action_tracker-1.0.2.gem rename to discovery/tests/testfiles/rubygems/action_tracker-1.0.2.gem diff --git a/minecode/tests/discovery/testfiles/rubygems/action_tracker-1.0.2.gem.package.json b/discovery/tests/testfiles/rubygems/action_tracker-1.0.2.gem.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/action_tracker-1.0.2.gem.package.json rename to discovery/tests/testfiles/rubygems/action_tracker-1.0.2.gem.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/apiv1/0xffffff.api.json b/discovery/tests/testfiles/rubygems/apiv1/0xffffff.api.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/apiv1/0xffffff.api.json rename to discovery/tests/testfiles/rubygems/apiv1/0xffffff.api.json diff --git a/minecode/tests/discovery/testfiles/rubygems/apiv1/0xffffff.api.package.json b/discovery/tests/testfiles/rubygems/apiv1/0xffffff.api.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/apiv1/0xffffff.api.package.json rename to discovery/tests/testfiles/rubygems/apiv1/0xffffff.api.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.json b/discovery/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.json rename to discovery/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.json diff --git a/minecode/tests/discovery/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.mapped.json b/discovery/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.mapped.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.mapped.json rename to discovery/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.mapped.json diff --git a/minecode/tests/discovery/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.package.json b/discovery/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.package.json rename to discovery/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/apiv1/action_tracker.api.json b/discovery/tests/testfiles/rubygems/apiv1/action_tracker.api.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/apiv1/action_tracker.api.json rename to discovery/tests/testfiles/rubygems/apiv1/action_tracker.api.json diff --git a/minecode/tests/discovery/testfiles/rubygems/apiv1/action_tracker.api.package.json b/discovery/tests/testfiles/rubygems/apiv1/action_tracker.api.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/apiv1/action_tracker.api.package.json rename to discovery/tests/testfiles/rubygems/apiv1/action_tracker.api.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/apiv1/expected_0xffffff.api.json b/discovery/tests/testfiles/rubygems/apiv1/expected_0xffffff.api.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/apiv1/expected_0xffffff.api.json rename to discovery/tests/testfiles/rubygems/apiv1/expected_0xffffff.api.json diff --git a/minecode/tests/discovery/testfiles/rubygems/apiv1/expected_a1630ty_a1630ty.api.json b/discovery/tests/testfiles/rubygems/apiv1/expected_a1630ty_a1630ty.api.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/apiv1/expected_a1630ty_a1630ty.api.json rename to discovery/tests/testfiles/rubygems/apiv1/expected_a1630ty_a1630ty.api.json diff --git a/minecode/tests/discovery/testfiles/rubygems/apiv1/expected_zuck.api.json b/discovery/tests/testfiles/rubygems/apiv1/expected_zuck.api.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/apiv1/expected_zuck.api.json rename to discovery/tests/testfiles/rubygems/apiv1/expected_zuck.api.json diff --git a/minecode/tests/discovery/testfiles/rubygems/apiv1/zuck.api.json b/discovery/tests/testfiles/rubygems/apiv1/zuck.api.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/apiv1/zuck.api.json rename to discovery/tests/testfiles/rubygems/apiv1/zuck.api.json diff --git a/minecode/tests/discovery/testfiles/rubygems/apiv1/zuck.api.package.json b/discovery/tests/testfiles/rubygems/apiv1/zuck.api.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/apiv1/zuck.api.package.json rename to discovery/tests/testfiles/rubygems/apiv1/zuck.api.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/archive-tar-minitar-0.5.2.gem b/discovery/tests/testfiles/rubygems/archive-tar-minitar-0.5.2.gem similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/archive-tar-minitar-0.5.2.gem rename to discovery/tests/testfiles/rubygems/archive-tar-minitar-0.5.2.gem diff --git a/minecode/tests/discovery/testfiles/rubygems/archive-tar-minitar-0.5.2.gem.package.json b/discovery/tests/testfiles/rubygems/archive-tar-minitar-0.5.2.gem.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/archive-tar-minitar-0.5.2.gem.package.json rename to discovery/tests/testfiles/rubygems/archive-tar-minitar-0.5.2.gem.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/blankslate-3.1.3.gem b/discovery/tests/testfiles/rubygems/blankslate-3.1.3.gem similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/blankslate-3.1.3.gem rename to discovery/tests/testfiles/rubygems/blankslate-3.1.3.gem diff --git a/minecode/tests/discovery/testfiles/rubygems/blankslate-3.1.3.gem.package.json b/discovery/tests/testfiles/rubygems/blankslate-3.1.3.gem.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/blankslate-3.1.3.gem.package.json rename to discovery/tests/testfiles/rubygems/blankslate-3.1.3.gem.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/gemspec/address_standardization.gemspec b/discovery/tests/testfiles/rubygems/gemspec/address_standardization.gemspec similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/gemspec/address_standardization.gemspec rename to discovery/tests/testfiles/rubygems/gemspec/address_standardization.gemspec diff --git a/minecode/tests/discovery/testfiles/rubygems/gemspec/arel.gemspec b/discovery/tests/testfiles/rubygems/gemspec/arel.gemspec similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/gemspec/arel.gemspec rename to discovery/tests/testfiles/rubygems/gemspec/arel.gemspec diff --git a/minecode/tests/discovery/testfiles/rubygems/index/latest_specs.4.8.gz b/discovery/tests/testfiles/rubygems/index/latest_specs.4.8.gz similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/index/latest_specs.4.8.gz rename to discovery/tests/testfiles/rubygems/index/latest_specs.4.8.gz diff --git a/minecode/tests/discovery/testfiles/rubygems/index/latest_specs.4.8.gz.expected.json b/discovery/tests/testfiles/rubygems/index/latest_specs.4.8.gz.expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/index/latest_specs.4.8.gz.expected.json rename to discovery/tests/testfiles/rubygems/index/latest_specs.4.8.gz.expected.json diff --git a/minecode/tests/discovery/testfiles/rubygems/m2r-2.1.0.gem b/discovery/tests/testfiles/rubygems/m2r-2.1.0.gem similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/m2r-2.1.0.gem rename to discovery/tests/testfiles/rubygems/m2r-2.1.0.gem diff --git a/minecode/tests/discovery/testfiles/rubygems/m2r-2.1.0.gem.package.json b/discovery/tests/testfiles/rubygems/m2r-2.1.0.gem.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/m2r-2.1.0.gem.package.json rename to discovery/tests/testfiles/rubygems/m2r-2.1.0.gem.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem b/discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem rename to discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem diff --git a/minecode/tests/discovery/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.mapped.json b/discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.mapped.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.mapped.json rename to discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.mapped.json diff --git a/minecode/tests/discovery/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.metadata b/discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.metadata similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.metadata rename to discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.metadata diff --git a/minecode/tests/discovery/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.package.json b/discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.package.json rename to discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem b/discovery/tests/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem rename to discovery/tests/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem diff --git a/minecode/tests/discovery/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem.package.json b/discovery/tests/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem.package.json rename to discovery/tests/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/ng-rails-csrf-0.1.0.gem b/discovery/tests/testfiles/rubygems/ng-rails-csrf-0.1.0.gem similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/ng-rails-csrf-0.1.0.gem rename to discovery/tests/testfiles/rubygems/ng-rails-csrf-0.1.0.gem diff --git a/minecode/tests/discovery/testfiles/rubygems/ng-rails-csrf-0.1.0.gem.package.json b/discovery/tests/testfiles/rubygems/ng-rails-csrf-0.1.0.gem.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/ng-rails-csrf-0.1.0.gem.package.json rename to discovery/tests/testfiles/rubygems/ng-rails-csrf-0.1.0.gem.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/small-0.2.gem b/discovery/tests/testfiles/rubygems/small-0.2.gem similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/small-0.2.gem rename to discovery/tests/testfiles/rubygems/small-0.2.gem diff --git a/minecode/tests/discovery/testfiles/rubygems/small-0.2.gem.package.json b/discovery/tests/testfiles/rubygems/small-0.2.gem.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/small-0.2.gem.package.json rename to discovery/tests/testfiles/rubygems/small-0.2.gem.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/small_wonder-0.1.10.gem b/discovery/tests/testfiles/rubygems/small_wonder-0.1.10.gem similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/small_wonder-0.1.10.gem rename to discovery/tests/testfiles/rubygems/small_wonder-0.1.10.gem diff --git a/minecode/tests/discovery/testfiles/rubygems/small_wonder-0.1.10.gem.package.json b/discovery/tests/testfiles/rubygems/small_wonder-0.1.10.gem.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/small_wonder-0.1.10.gem.package.json rename to discovery/tests/testfiles/rubygems/small_wonder-0.1.10.gem.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem b/discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem rename to discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem diff --git a/minecode/tests/discovery/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.mapped.json b/discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.mapped.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.mapped.json rename to discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.mapped.json diff --git a/minecode/tests/discovery/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.package.json b/discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.package.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.package.json rename to discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.package.json diff --git a/minecode/tests/discovery/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.visited.json b/discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.visited.json similarity index 100% rename from minecode/tests/discovery/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.visited.json rename to discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.visited.json diff --git a/minecode/tests/discovery/testfiles/run_map/test_map_uri_does_update_with_same_mining_level-expected.json b/discovery/tests/testfiles/run_map/test_map_uri_does_update_with_same_mining_level-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/run_map/test_map_uri_does_update_with_same_mining_level-expected.json rename to discovery/tests/testfiles/run_map/test_map_uri_does_update_with_same_mining_level-expected.json diff --git a/minecode/tests/discovery/testfiles/run_map/test_map_uri_replace_with_new_with_higher_new_mining_level-expected.json b/discovery/tests/testfiles/run_map/test_map_uri_replace_with_new_with_higher_new_mining_level-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/run_map/test_map_uri_replace_with_new_with_higher_new_mining_level-expected.json rename to discovery/tests/testfiles/run_map/test_map_uri_replace_with_new_with_higher_new_mining_level-expected.json diff --git a/minecode/tests/discovery/testfiles/run_map/test_map_uri_update_only_empties_with_lesser_new_mining_level-expected.json b/discovery/tests/testfiles/run_map/test_map_uri_update_only_empties_with_lesser_new_mining_level-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/run_map/test_map_uri_update_only_empties_with_lesser_new_mining_level-expected.json rename to discovery/tests/testfiles/run_map/test_map_uri_update_only_empties_with_lesser_new_mining_level-expected.json diff --git a/minecode/tests/discovery/testfiles/run_map/test_merge_packages_no_replace-expected.json b/discovery/tests/testfiles/run_map/test_merge_packages_no_replace-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/run_map/test_merge_packages_no_replace-expected.json rename to discovery/tests/testfiles/run_map/test_merge_packages_no_replace-expected.json diff --git a/minecode/tests/discovery/testfiles/run_map/test_merge_packages_with_replace-expected.json b/discovery/tests/testfiles/run_map/test_merge_packages_with_replace-expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/run_map/test_merge_packages_with_replace-expected.json rename to discovery/tests/testfiles/run_map/test_merge_packages_with_replace-expected.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/a-vitkus_profile.html b/discovery/tests/testfiles/sourceforge/a-vitkus_profile.html similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/a-vitkus_profile.html rename to discovery/tests/testfiles/sourceforge/a-vitkus_profile.html diff --git a/minecode/tests/discovery/testfiles/sourceforge/expected_heanet_rsync_dir.json b/discovery/tests/testfiles/sourceforge/expected_heanet_rsync_dir.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/expected_heanet_rsync_dir.json rename to discovery/tests/testfiles/sourceforge/expected_heanet_rsync_dir.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/expected_netwiki.json b/discovery/tests/testfiles/sourceforge/expected_netwiki.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/expected_netwiki.json rename to discovery/tests/testfiles/sourceforge/expected_netwiki.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/expected_sf_dir_index.json b/discovery/tests/testfiles/sourceforge/expected_sf_dir_index.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/expected_sf_dir_index.json rename to discovery/tests/testfiles/sourceforge/expected_sf_dir_index.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/expected_sf_dir_page.json b/discovery/tests/testfiles/sourceforge/expected_sf_dir_page.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/expected_sf_dir_page.json rename to discovery/tests/testfiles/sourceforge/expected_sf_dir_page.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/expected_sf_project.json b/discovery/tests/testfiles/sourceforge/expected_sf_project.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/expected_sf_project.json rename to discovery/tests/testfiles/sourceforge/expected_sf_project.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/expected_sf_sitemap.json b/discovery/tests/testfiles/sourceforge/expected_sf_sitemap.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/expected_sf_sitemap.json rename to discovery/tests/testfiles/sourceforge/expected_sf_sitemap.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/expected_sf_sitemap_new.json b/discovery/tests/testfiles/sourceforge/expected_sf_sitemap_new.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/expected_sf_sitemap_new.json rename to discovery/tests/testfiles/sourceforge/expected_sf_sitemap_new.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/expected_sf_sitemap_page.json b/discovery/tests/testfiles/sourceforge/expected_sf_sitemap_page.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/expected_sf_sitemap_page.json rename to discovery/tests/testfiles/sourceforge/expected_sf_sitemap_page.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/expected_sf_sitemap_page_new.json b/discovery/tests/testfiles/sourceforge/expected_sf_sitemap_page_new.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/expected_sf_sitemap_page_new.json rename to discovery/tests/testfiles/sourceforge/expected_sf_sitemap_page_new.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/expected_sitemap-6.json b/discovery/tests/testfiles/sourceforge/expected_sitemap-6.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/expected_sitemap-6.json rename to discovery/tests/testfiles/sourceforge/expected_sitemap-6.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/filezilla.json b/discovery/tests/testfiles/sourceforge/filezilla.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/filezilla.json rename to discovery/tests/testfiles/sourceforge/filezilla.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/mapper_niftyphp_expected.json b/discovery/tests/testfiles/sourceforge/mapper_niftyphp_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/mapper_niftyphp_expected.json rename to discovery/tests/testfiles/sourceforge/mapper_niftyphp_expected.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/mapper_odanur_expected.json b/discovery/tests/testfiles/sourceforge/mapper_odanur_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/mapper_odanur_expected.json rename to discovery/tests/testfiles/sourceforge/mapper_odanur_expected.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/mapper_omonoql_expected.json b/discovery/tests/testfiles/sourceforge/mapper_omonoql_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/mapper_omonoql_expected.json rename to discovery/tests/testfiles/sourceforge/mapper_omonoql_expected.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/mapper_openstunts_expected.json b/discovery/tests/testfiles/sourceforge/mapper_openstunts_expected.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/mapper_openstunts_expected.json rename to discovery/tests/testfiles/sourceforge/mapper_openstunts_expected.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/monoql.json b/discovery/tests/testfiles/sourceforge/monoql.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/monoql.json rename to discovery/tests/testfiles/sourceforge/monoql.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/netwiki.json b/discovery/tests/testfiles/sourceforge/netwiki.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/netwiki.json rename to discovery/tests/testfiles/sourceforge/netwiki.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/niftyphp.json b/discovery/tests/testfiles/sourceforge/niftyphp.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/niftyphp.json rename to discovery/tests/testfiles/sourceforge/niftyphp.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/odanur.json b/discovery/tests/testfiles/sourceforge/odanur.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/odanur.json rename to discovery/tests/testfiles/sourceforge/odanur.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/openstunts.json b/discovery/tests/testfiles/sourceforge/openstunts.json similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/openstunts.json rename to discovery/tests/testfiles/sourceforge/openstunts.json diff --git a/minecode/tests/discovery/testfiles/sourceforge/rsync_heanet_sfnet.dir b/discovery/tests/testfiles/sourceforge/rsync_heanet_sfnet.dir similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/rsync_heanet_sfnet.dir rename to discovery/tests/testfiles/sourceforge/rsync_heanet_sfnet.dir diff --git a/minecode/tests/discovery/testfiles/sourceforge/sitemap-1.xml b/discovery/tests/testfiles/sourceforge/sitemap-1.xml similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/sitemap-1.xml rename to discovery/tests/testfiles/sourceforge/sitemap-1.xml diff --git a/minecode/tests/discovery/testfiles/sourceforge/sitemap-6.xml b/discovery/tests/testfiles/sourceforge/sitemap-6.xml similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/sitemap-6.xml rename to discovery/tests/testfiles/sourceforge/sitemap-6.xml diff --git a/minecode/tests/discovery/testfiles/sourceforge/sitemap.xml b/discovery/tests/testfiles/sourceforge/sitemap.xml similarity index 100% rename from minecode/tests/discovery/testfiles/sourceforge/sitemap.xml rename to discovery/tests/testfiles/sourceforge/sitemap.xml diff --git a/minecode/src/discovery/utils.py b/discovery/utils.py similarity index 100% rename from minecode/src/discovery/utils.py rename to discovery/utils.py diff --git a/minecode/src/discovery/utils_test.py b/discovery/utils_test.py similarity index 100% rename from minecode/src/discovery/utils_test.py rename to discovery/utils_test.py diff --git a/minecode/src/discovery/utils_test.py.ABOUT b/discovery/utils_test.py.ABOUT similarity index 100% rename from minecode/src/discovery/utils_test.py.ABOUT rename to discovery/utils_test.py.ABOUT diff --git a/minecode/src/discovery/utils_test.py.NOTICE b/discovery/utils_test.py.NOTICE similarity index 100% rename from minecode/src/discovery/utils_test.py.NOTICE rename to discovery/utils_test.py.NOTICE diff --git a/minecode/src/discovery/version.py b/discovery/version.py similarity index 100% rename from minecode/src/discovery/version.py rename to discovery/version.py diff --git a/minecode/src/discovery/visitors/__init__.py b/discovery/visitors/__init__.py similarity index 100% rename from minecode/src/discovery/visitors/__init__.py rename to discovery/visitors/__init__.py diff --git a/minecode/src/discovery/visitors/debian.py b/discovery/visitors/debian.py similarity index 100% rename from minecode/src/discovery/visitors/debian.py rename to discovery/visitors/debian.py diff --git a/minecode/src/discovery/visitors/fdroid.py b/discovery/visitors/fdroid.py similarity index 100% rename from minecode/src/discovery/visitors/fdroid.py rename to discovery/visitors/fdroid.py diff --git a/minecode/src/discovery/visitors/freebsd.py b/discovery/visitors/freebsd.py similarity index 100% rename from minecode/src/discovery/visitors/freebsd.py rename to discovery/visitors/freebsd.py diff --git a/minecode/src/discovery/visitors/java_stream.LICENSE b/discovery/visitors/java_stream.LICENSE similarity index 100% rename from minecode/src/discovery/visitors/java_stream.LICENSE rename to discovery/visitors/java_stream.LICENSE diff --git a/minecode/src/discovery/visitors/java_stream.py b/discovery/visitors/java_stream.py similarity index 100% rename from minecode/src/discovery/visitors/java_stream.py rename to discovery/visitors/java_stream.py diff --git a/minecode/src/discovery/visitors/java_stream.py.ABOUT b/discovery/visitors/java_stream.py.ABOUT similarity index 100% rename from minecode/src/discovery/visitors/java_stream.py.ABOUT rename to discovery/visitors/java_stream.py.ABOUT diff --git a/minecode/src/discovery/visitors/maven.py b/discovery/visitors/maven.py similarity index 100% rename from minecode/src/discovery/visitors/maven.py rename to discovery/visitors/maven.py diff --git a/minecode/src/discovery/visitors/npm.py b/discovery/visitors/npm.py similarity index 100% rename from minecode/src/discovery/visitors/npm.py rename to discovery/visitors/npm.py diff --git a/minecode/src/discovery/visitors/pypi.py b/discovery/visitors/pypi.py similarity index 100% rename from minecode/src/discovery/visitors/pypi.py rename to discovery/visitors/pypi.py diff --git a/minecode/src/discovery/visitors/rubygems.py b/discovery/visitors/rubygems.py similarity index 100% rename from minecode/src/discovery/visitors/rubygems.py rename to discovery/visitors/rubygems.py diff --git a/minecode/src/discovery/visitors/sourceforge.py b/discovery/visitors/sourceforge.py similarity index 100% rename from minecode/src/discovery/visitors/sourceforge.py rename to discovery/visitors/sourceforge.py diff --git a/matchcode/manage.py b/manage.py similarity index 87% rename from matchcode/manage.py rename to manage.py index 88d9004d..f9bdd906 100644 --- a/matchcode/manage.py +++ b/manage.py @@ -15,5 +15,5 @@ if __name__ == '__main__': from django.core.management import execute_from_command_line - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'matchcodeio.settings') + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'purldb.settings') execute_from_command_line(sys.argv) diff --git a/matchcode/.gitignore b/matchcode/.gitignore deleted file mode 100644 index c8555a1a..00000000 --- a/matchcode/.gitignore +++ /dev/null @@ -1,75 +0,0 @@ -# Python compiled files -*.py[cod] - -# virtualenv and other misc bits -*.egg-info -/dist -/build -/bin -/lib -/scripts -/Scripts -/Lib -/pip-selfcheck.json -/tmp -/venv -.Python -/include -/Include -/local -*/local/* -/local/ -/share/ -/tcl/ -/.eggs/ -pip-wheel-metadata/ -/venv - -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.cache -.coverage -.coverage.* -nosetests.xml -htmlcov - -# Translations -*.mo - -# IDEs -.project -.pydevproject -.idea -org.eclipse.core.resources.prefs -.vscode -.vs - -# Sphinx -docs/_build -docs/bin -docs/build -docs/include -docs/Lib -doc/pyvenv.cfg -pyvenv.cfg - -# Various junk and temp files -.DS_Store -*~ -.*.sw[po] -.build -.ve -*.bak -/.cache/ - -# pyenv -/.python-version -/man/ -/.pytest_cache/ -lib64 -tcl - -# Ignore Jupyter Notebook related temp files -.ipynb_checkpoints/ diff --git a/minecode/src/clearindex/__init__.py b/matchcode/__init__.py similarity index 100% rename from minecode/src/clearindex/__init__.py rename to matchcode/__init__.py diff --git a/matchcode/apache-2.0.LICENSE b/matchcode/apache-2.0.LICENSE deleted file mode 100644 index 261eeb9e..00000000 --- a/matchcode/apache-2.0.LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/matchcode/src/matchcode/api.py b/matchcode/api.py similarity index 100% rename from matchcode/src/matchcode/api.py rename to matchcode/api.py diff --git a/matchcode/src/matchcode/fingerprinting.py b/matchcode/fingerprinting.py similarity index 100% rename from matchcode/src/matchcode/fingerprinting.py rename to matchcode/fingerprinting.py diff --git a/matchcode/src/matchcode/halohash.py b/matchcode/halohash.py similarity index 100% rename from matchcode/src/matchcode/halohash.py rename to matchcode/halohash.py diff --git a/matchcode/src/matchcode/hash.py b/matchcode/hash.py similarity index 100% rename from matchcode/src/matchcode/hash.py rename to matchcode/hash.py diff --git a/matchcode/src/matchcode/indexing.py b/matchcode/indexing.py similarity index 100% rename from matchcode/src/matchcode/indexing.py rename to matchcode/indexing.py diff --git a/minecode/src/clearindex/management/__init__.py b/matchcode/management/__init__.py similarity index 100% rename from minecode/src/clearindex/management/__init__.py rename to matchcode/management/__init__.py diff --git a/matchcode/src/matchcode/management/commands/__init__.py b/matchcode/management/commands/__init__.py similarity index 100% rename from matchcode/src/matchcode/management/commands/__init__.py rename to matchcode/management/commands/__init__.py diff --git a/matchcode/src/matchcode/management/commands/index_packages.py b/matchcode/management/commands/index_packages.py similarity index 100% rename from matchcode/src/matchcode/management/commands/index_packages.py rename to matchcode/management/commands/index_packages.py diff --git a/matchcode/src/matchcode/management/commands/match_scan.py b/matchcode/management/commands/match_scan.py similarity index 100% rename from matchcode/src/matchcode/management/commands/match_scan.py rename to matchcode/management/commands/match_scan.py diff --git a/matchcode/src/matchcode/match.py b/matchcode/match.py similarity index 100% rename from matchcode/src/matchcode/match.py rename to matchcode/match.py diff --git a/matchcode/src/matchcode/migrations/0001_initial.py b/matchcode/migrations/0001_initial.py similarity index 100% rename from matchcode/src/matchcode/migrations/0001_initial.py rename to matchcode/migrations/0001_initial.py diff --git a/matchcode/src/matchcode/migrations/0002_alter_approximatedirectorycontentindex_package_and_more.py b/matchcode/migrations/0002_alter_approximatedirectorycontentindex_package_and_more.py similarity index 100% rename from matchcode/src/matchcode/migrations/0002_alter_approximatedirectorycontentindex_package_and_more.py rename to matchcode/migrations/0002_alter_approximatedirectorycontentindex_package_and_more.py diff --git a/minecode/src/clearindex/management/commands/__init__.py b/matchcode/migrations/__init__.py similarity index 100% rename from minecode/src/clearindex/management/commands/__init__.py rename to matchcode/migrations/__init__.py diff --git a/matchcode/src/matchcode/models.py b/matchcode/models.py similarity index 100% rename from matchcode/src/matchcode/models.py rename to matchcode/models.py diff --git a/matchcode/src/matchcode/plugin_match.py b/matchcode/plugin_match.py similarity index 100% rename from matchcode/src/matchcode/plugin_match.py rename to matchcode/plugin_match.py diff --git a/matchcode/src/matchcode/utils.py b/matchcode/utils.py similarity index 100% rename from matchcode/src/matchcode/utils.py rename to matchcode/utils.py diff --git a/minecode/.gitattributes b/minecode/.gitattributes deleted file mode 100644 index 96c89ceb..00000000 --- a/minecode/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -# Ignore all Git auto CR/LF line endings conversions -* -text -pyproject.toml export-subst diff --git a/minecode/.gitignore b/minecode/.gitignore deleted file mode 100644 index 339dca50..00000000 --- a/minecode/.gitignore +++ /dev/null @@ -1,73 +0,0 @@ -# Python compiled files -*.py[cod] - -# virtualenv and other misc bits -*.egg-info -/dist -/build -/bin -/lib -/scripts -/Scripts -/Lib -/pip-selfcheck.json -/tmp -/venv -.Python -/include -/Include -/local -*/local/* -/local/ -/share/ -/tcl/ -/.eggs/ - -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.cache -.coverage -.coverage.* -nosetests.xml -htmlcov - -# Translations -*.mo - -# IDEs -.project -.pydevproject -.idea -org.eclipse.core.resources.prefs -.vscode -.vs - -# Sphinx -docs/_build -docs/bin -docs/build -docs/include -docs/Lib -doc/pyvenv.cfg -pyvenv.cfg - -# Various junk and temp files -.DS_Store -*~ -.*.sw[po] -.build -.ve -*.bak -/.cache/ - -# pyenv -/.python-version -/man/ -/.pytest_cache/ -lib64 -tcl - -# Ignore Jupyter Notebook related temp files -.ipynb_checkpoints/ diff --git a/minecode/MANIFEST.in b/minecode/MANIFEST.in deleted file mode 100644 index 613dbab5..00000000 --- a/minecode/MANIFEST.in +++ /dev/null @@ -1,21 +0,0 @@ -graft etc -graft src -graft tests - -include *.LICENSE -include NOTICE -include *.ABOUT -include *.toml -include *.yml -include *.rst -include setup.* -include configure* -include requirements* -include .git* -include MANIFEST.in -include setup.cfg -include setup.py - - -global-exclude *.py[co] __pycache__ *.*~ - diff --git a/minecode/Makefile b/minecode/Makefile deleted file mode 100644 index 9514ddc6..00000000 --- a/minecode/Makefile +++ /dev/null @@ -1,123 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -# Python version can be specified with `$ PYTHON_EXE=python3.x make conf` -PYTHON_EXE?=python3 -VENV=venv -MANAGE=${VENV}/bin/python manage.py -ACTIVATE?=. ${VENV}/bin/activate; -VIRTUALENV_PYZ=../etc/thirdparty/virtualenv.pyz -# Do not depend on Python to generate the SECRET_KEY -GET_SECRET_KEY=`base64 /dev/urandom | head -c50` -# Customize with `$ make envfile ENV_FILE=/etc/purldb/.env` -ENV_FILE=.env -# Customize with `$ make postgres PACKAGEDB_DB_PASSWORD=YOUR_PASSWORD` -PACKAGEDB_DB_PASSWORD=packagedb - -# Use sudo for postgres, but only on Linux -UNAME := $(shell uname) -ifeq ($(UNAME), Linux) - SUDO_POSTGRES=sudo -u postgres -else - SUDO_POSTGRES= -endif - -virtualenv: - @echo "-> Bootstrap the virtualenv with PYTHON_EXE=${PYTHON_EXE}" - @${PYTHON_EXE} ${VIRTUALENV_PYZ} --never-download --no-periodic-update ${VENV} - -conf: - @echo "-> Install dependencies" - @./configure - -dev: - @echo "-> Configure and install development dependencies" - @./configure --dev - -envfile: - @echo "-> Create the .env file and generate a secret key" - @if test -f ${ENV_FILE}; then echo ".env file exists already"; exit 1; fi - @mkdir -p $(shell dirname ${ENV_FILE}) && touch ${ENV_FILE} - @echo SECRET_KEY=\"${GET_SECRET_KEY}\" > ${ENV_FILE} - -isort: - @echo "-> Apply isort changes to ensure proper imports ordering" - ${VENV}/bin/isort . - -black: - @echo "-> Apply black code formatter" - ${VENV}/bin/black . - -doc8: - @echo "-> Run doc8 validation" - @${ACTIVATE} doc8 --max-line-length 100 --ignore-path docs/_build/ --quiet docs/ - -valid: isort black - -check: - @echo "-> Run pycodestyle (PEP8) validation" - @${ACTIVATE} pycodestyle --max-line-length=100 --exclude=venv,lib,thirdparty,docs,migrations,settings.py . - @echo "-> Run isort imports ordering validation" - @${ACTIVATE} isort --check-only . - @echo "-> Run black validation" - @${ACTIVATE} black --check ${BLACK_ARGS} - -clean: - @echo "-> Clean the Python env" - @./configure --clean - -migrate: - @echo "-> Apply database migrations" - ${MANAGE} migrate - -postgres: - @echo "-> Configure PostgreSQL database" - @echo "-> Create database user 'packagedb'" - ${SUDO_POSTGRES} createuser --no-createrole --no-superuser --login --inherit --createdb packagedb || true - ${SUDO_POSTGRES} psql -c "alter user packagedb with encrypted password '${PACKAGEDB_DB_PASSWORD}';" || true - @echo "-> Drop 'packagedb' database" - ${SUDO_POSTGRES} dropdb packagedb || true - @echo "-> Create 'packagedb' database" - ${SUDO_POSTGRES} createdb --encoding=utf-8 --owner=packagedb packagedb - @$(MAKE) migrate - -run: - ${MANAGE} runserver 8001 --insecure - -test: - @echo "-> Run the test suite" - ${ACTIVATE} DJANGO_SETTINGS_MODULE=minecodeio.settings ${PYTHON_EXE} -m pytest -vvs - -shell: - ${MANAGE} shell - -clearsync: - ${ACTIVATE} clearsync --save-to-db --verbose -n 3 - -clearindex: - ${MANAGE} run_clearindex - -bump: - @echo "-> Bump the version" - bin/bumpver update --no-fetch --patch - -docs: - rm -rf docs/_build/ - @${ACTIVATE} sphinx-build docs/ docs/_build/ - -docker-images: - @echo "-> Build Docker services" - docker-compose build - @echo "-> Pull service images" - docker-compose pull - @echo "-> Save the service images to a compressed tar archive in the dist/ directory" - @mkdir -p dist/ - @docker save postgres packagedb_packagedb nginx | gzip > dist/packagedb-images-`git describe --tags`.tar.gz - -.PHONY: virtualenv conf dev envfile isort black doc8 valid check clean migrate postgres run test shell clearsync clearindex bump docs docker-images diff --git a/minecode/apache-2.0.LICENSE b/minecode/apache-2.0.LICENSE deleted file mode 100644 index 261eeb9e..00000000 --- a/minecode/apache-2.0.LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/minecode/cc-by-sa-4.0.LICENSE b/minecode/cc-by-sa-4.0.LICENSE deleted file mode 100644 index e04b480f..00000000 --- a/minecode/cc-by-sa-4.0.LICENSE +++ /dev/null @@ -1,427 +0,0 @@ -Attribution-ShareAlike 4.0 International - -======================================================================= - -Creative Commons Corporation ("Creative Commons") is not a law firm and -does not provide legal services or legal advice. Distribution of -Creative Commons public licenses does not create a lawyer-client or -other relationship. Creative Commons makes its licenses and related -information available on an "as-is" basis. Creative Commons gives no -warranties regarding its licenses, any material licensed under their -terms and conditions, or any related information. Creative Commons -disclaims all liability for damages resulting from their use to the -fullest extent possible. - -Using Creative Commons Public Licenses - -Creative Commons public licenses provide a standard set of terms and -conditions that creators and other rights holders may use to share -original works of authorship and other material subject to copyright -and certain other rights specified in the public license below. The -following considerations are for informational purposes only, are not -exhaustive, and do not form part of our licenses. - - Considerations for licensors: Our public licenses are - intended for use by those authorized to give the public - permission to use material in ways otherwise restricted by - copyright and certain other rights. Our licenses are - irrevocable. Licensors should read and understand the terms - and conditions of the license they choose before applying it. - Licensors should also secure all rights necessary before - applying our licenses so that the public can reuse the - material as expected. Licensors should clearly mark any - material not subject to the license. This includes other CC- - licensed material, or material used under an exception or - limitation to copyright. More considerations for licensors: - wiki.creativecommons.org/Considerations_for_licensors - - Considerations for the public: By using one of our public - licenses, a licensor grants the public permission to use the - licensed material under specified terms and conditions. If - the licensor's permission is not necessary for any reason--for - example, because of any applicable exception or limitation to - copyright--then that use is not regulated by the license. Our - licenses grant only permissions under copyright and certain - other rights that a licensor has authority to grant. Use of - the licensed material may still be restricted for other - reasons, including because others have copyright or other - rights in the material. A licensor may make special requests, - such as asking that all changes be marked or described. - Although not required by our licenses, you are encouraged to - respect those requests where reasonable. More considerations - for the public: - wiki.creativecommons.org/Considerations_for_licensees - -======================================================================= - -Creative Commons Attribution-ShareAlike 4.0 International Public -License - -By exercising the Licensed Rights (defined below), You accept and agree -to be bound by the terms and conditions of this Creative Commons -Attribution-ShareAlike 4.0 International Public License ("Public -License"). To the extent this Public License may be interpreted as a -contract, You are granted the Licensed Rights in consideration of Your -acceptance of these terms and conditions, and the Licensor grants You -such rights in consideration of benefits the Licensor receives from -making the Licensed Material available under these terms and -conditions. - - -Section 1 -- Definitions. - - a. Adapted Material means material subject to Copyright and Similar - Rights that is derived from or based upon the Licensed Material - and in which the Licensed Material is translated, altered, - arranged, transformed, or otherwise modified in a manner requiring - permission under the Copyright and Similar Rights held by the - Licensor. For purposes of this Public License, where the Licensed - Material is a musical work, performance, or sound recording, - Adapted Material is always produced where the Licensed Material is - synched in timed relation with a moving image. - - b. Adapter's License means the license You apply to Your Copyright - and Similar Rights in Your contributions to Adapted Material in - accordance with the terms and conditions of this Public License. - - c. BY-SA Compatible License means a license listed at - creativecommons.org/compatiblelicenses, approved by Creative - Commons as essentially the equivalent of this Public License. - - d. Copyright and Similar Rights means copyright and/or similar rights - closely related to copyright including, without limitation, - performance, broadcast, sound recording, and Sui Generis Database - Rights, without regard to how the rights are labeled or - categorized. For purposes of this Public License, the rights - specified in Section 2(b)(1)-(2) are not Copyright and Similar - Rights. - - e. Effective Technological Measures means those measures that, in the - absence of proper authority, may not be circumvented under laws - fulfilling obligations under Article 11 of the WIPO Copyright - Treaty adopted on December 20, 1996, and/or similar international - agreements. - - f. Exceptions and Limitations means fair use, fair dealing, and/or - any other exception or limitation to Copyright and Similar Rights - that applies to Your use of the Licensed Material. - - g. License Elements means the license attributes listed in the name - of a Creative Commons Public License. The License Elements of this - Public License are Attribution and ShareAlike. - - h. Licensed Material means the artistic or literary work, database, - or other material to which the Licensor applied this Public - License. - - i. Licensed Rights means the rights granted to You subject to the - terms and conditions of this Public License, which are limited to - all Copyright and Similar Rights that apply to Your use of the - Licensed Material and that the Licensor has authority to license. - - j. Licensor means the individual(s) or entity(ies) granting rights - under this Public License. - - k. Share means to provide material to the public by any means or - process that requires permission under the Licensed Rights, such - as reproduction, public display, public performance, distribution, - dissemination, communication, or importation, and to make material - available to the public including in ways that members of the - public may access the material from a place and at a time - individually chosen by them. - - l. Sui Generis Database Rights means rights other than copyright - resulting from Directive 96/9/EC of the European Parliament and of - the Council of 11 March 1996 on the legal protection of databases, - as amended and/or succeeded, as well as other essentially - equivalent rights anywhere in the world. - - m. You means the individual or entity exercising the Licensed Rights - under this Public License. Your has a corresponding meaning. - - -Section 2 -- Scope. - - a. License grant. - - 1. Subject to the terms and conditions of this Public License, - the Licensor hereby grants You a worldwide, royalty-free, - non-sublicensable, non-exclusive, irrevocable license to - exercise the Licensed Rights in the Licensed Material to: - - a. reproduce and Share the Licensed Material, in whole or - in part; and - - b. produce, reproduce, and Share Adapted Material. - - 2. Exceptions and Limitations. For the avoidance of doubt, where - Exceptions and Limitations apply to Your use, this Public - License does not apply, and You do not need to comply with - its terms and conditions. - - 3. Term. The term of this Public License is specified in Section - 6(a). - - 4. Media and formats; technical modifications allowed. The - Licensor authorizes You to exercise the Licensed Rights in - all media and formats whether now known or hereafter created, - and to make technical modifications necessary to do so. The - Licensor waives and/or agrees not to assert any right or - authority to forbid You from making technical modifications - necessary to exercise the Licensed Rights, including - technical modifications necessary to circumvent Effective - Technological Measures. For purposes of this Public License, - simply making modifications authorized by this Section 2(a) - (4) never produces Adapted Material. - - 5. Downstream recipients. - - a. Offer from the Licensor -- Licensed Material. Every - recipient of the Licensed Material automatically - receives an offer from the Licensor to exercise the - Licensed Rights under the terms and conditions of this - Public License. - - b. Additional offer from the Licensor -- Adapted Material. - Every recipient of Adapted Material from You - automatically receives an offer from the Licensor to - exercise the Licensed Rights in the Adapted Material - under the conditions of the Adapter's License You apply. - - c. No downstream restrictions. You may not offer or impose - any additional or different terms or conditions on, or - apply any Effective Technological Measures to, the - Licensed Material if doing so restricts exercise of the - Licensed Rights by any recipient of the Licensed - Material. - - 6. No endorsement. Nothing in this Public License constitutes or - may be construed as permission to assert or imply that You - are, or that Your use of the Licensed Material is, connected - with, or sponsored, endorsed, or granted official status by, - the Licensor or others designated to receive attribution as - provided in Section 3(a)(1)(A)(i). - - b. Other rights. - - 1. Moral rights, such as the right of integrity, are not - licensed under this Public License, nor are publicity, - privacy, and/or other similar personality rights; however, to - the extent possible, the Licensor waives and/or agrees not to - assert any such rights held by the Licensor to the limited - extent necessary to allow You to exercise the Licensed - Rights, but not otherwise. - - 2. Patent and trademark rights are not licensed under this - Public License. - - 3. To the extent possible, the Licensor waives any right to - collect royalties from You for the exercise of the Licensed - Rights, whether directly or through a collecting society - under any voluntary or waivable statutory or compulsory - licensing scheme. In all other cases the Licensor expressly - reserves any right to collect such royalties. - - -Section 3 -- License Conditions. - -Your exercise of the Licensed Rights is expressly made subject to the -following conditions. - - a. Attribution. - - 1. If You Share the Licensed Material (including in modified - form), You must: - - a. retain the following if it is supplied by the Licensor - with the Licensed Material: - - i. identification of the creator(s) of the Licensed - Material and any others designated to receive - attribution, in any reasonable manner requested by - the Licensor (including by pseudonym if - designated); - - ii. a copyright notice; - - iii. a notice that refers to this Public License; - - iv. a notice that refers to the disclaimer of - warranties; - - v. a URI or hyperlink to the Licensed Material to the - extent reasonably practicable; - - b. indicate if You modified the Licensed Material and - retain an indication of any previous modifications; and - - c. indicate the Licensed Material is licensed under this - Public License, and include the text of, or the URI or - hyperlink to, this Public License. - - 2. You may satisfy the conditions in Section 3(a)(1) in any - reasonable manner based on the medium, means, and context in - which You Share the Licensed Material. For example, it may be - reasonable to satisfy the conditions by providing a URI or - hyperlink to a resource that includes the required - information. - - 3. If requested by the Licensor, You must remove any of the - information required by Section 3(a)(1)(A) to the extent - reasonably practicable. - - b. ShareAlike. - - In addition to the conditions in Section 3(a), if You Share - Adapted Material You produce, the following conditions also apply. - - 1. The Adapter's License You apply must be a Creative Commons - license with the same License Elements, this version or - later, or a BY-SA Compatible License. - - 2. You must include the text of, or the URI or hyperlink to, the - Adapter's License You apply. You may satisfy this condition - in any reasonable manner based on the medium, means, and - context in which You Share Adapted Material. - - 3. You may not offer or impose any additional or different terms - or conditions on, or apply any Effective Technological - Measures to, Adapted Material that restrict exercise of the - rights granted under the Adapter's License You apply. - - -Section 4 -- Sui Generis Database Rights. - -Where the Licensed Rights include Sui Generis Database Rights that -apply to Your use of the Licensed Material: - - a. for the avoidance of doubt, Section 2(a)(1) grants You the right - to extract, reuse, reproduce, and Share all or a substantial - portion of the contents of the database; - - b. if You include all or a substantial portion of the database - contents in a database in which You have Sui Generis Database - Rights, then the database in which You have Sui Generis Database - Rights (but not its individual contents) is Adapted Material, - - including for purposes of Section 3(b); and - c. You must comply with the conditions in Section 3(a) if You Share - all or a substantial portion of the contents of the database. - -For the avoidance of doubt, this Section 4 supplements and does not -replace Your obligations under this Public License where the Licensed -Rights include other Copyright and Similar Rights. - - -Section 5 -- Disclaimer of Warranties and Limitation of Liability. - - a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE - EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS - AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF - ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, - IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, - WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR - PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, - ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT - KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT - ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. - - b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE - TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, - NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, - INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, - COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR - USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN - ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR - DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR - IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. - - c. The disclaimer of warranties and limitation of liability provided - above shall be interpreted in a manner that, to the extent - possible, most closely approximates an absolute disclaimer and - waiver of all liability. - - -Section 6 -- Term and Termination. - - a. This Public License applies for the term of the Copyright and - Similar Rights licensed here. However, if You fail to comply with - this Public License, then Your rights under this Public License - terminate automatically. - - b. Where Your right to use the Licensed Material has terminated under - Section 6(a), it reinstates: - - 1. automatically as of the date the violation is cured, provided - it is cured within 30 days of Your discovery of the - violation; or - - 2. upon express reinstatement by the Licensor. - - For the avoidance of doubt, this Section 6(b) does not affect any - right the Licensor may have to seek remedies for Your violations - of this Public License. - - c. For the avoidance of doubt, the Licensor may also offer the - Licensed Material under separate terms or conditions or stop - distributing the Licensed Material at any time; however, doing so - will not terminate this Public License. - - d. Sections 1, 5, 6, 7, and 8 survive termination of this Public - License. - - -Section 7 -- Other Terms and Conditions. - - a. The Licensor shall not be bound by any additional or different - terms or conditions communicated by You unless expressly agreed. - - b. Any arrangements, understandings, or agreements regarding the - Licensed Material not stated herein are separate from and - independent of the terms and conditions of this Public License. - - -Section 8 -- Interpretation. - - a. For the avoidance of doubt, this Public License does not, and - shall not be interpreted to, reduce, limit, restrict, or impose - conditions on any use of the Licensed Material that could lawfully - be made without permission under this Public License. - - b. To the extent possible, if any provision of this Public License is - deemed unenforceable, it shall be automatically reformed to the - minimum extent necessary to make it enforceable. If the provision - cannot be reformed, it shall be severed from this Public License - without affecting the enforceability of the remaining terms and - conditions. - - c. No term or condition of this Public License will be waived and no - failure to comply consented to unless expressly agreed to by the - Licensor. - - d. Nothing in this Public License constitutes or may be interpreted - as a limitation upon, or waiver of, any privileges and immunities - that apply to the Licensor or You, including from the legal - processes of any jurisdiction or authority. - - -======================================================================= - -Creative Commons is not a party to its public -licenses. Notwithstanding, Creative Commons may elect to apply one of -its public licenses to material it publishes and in those instances -will be considered the “Licensor.” The text of the Creative Commons -public licenses is dedicated to the public domain under the CC0 Public -Domain Dedication. Except for the limited purpose of indicating that -material is shared under a Creative Commons public license or as -otherwise permitted by the Creative Commons policies published at -creativecommons.org/policies, Creative Commons does not authorize the -use of the trademark "Creative Commons" or any other trademark or logo -of Creative Commons without its prior written consent including, -without limitation, in connection with any unauthorized modifications -to any of its public licenses or any other arrangements, -understandings, or agreements concerning use of licensed material. For -the avoidance of doubt, this paragraph does not form part of the -public licenses. - -Creative Commons may be contacted at creativecommons.org. diff --git a/minecode/configure b/minecode/configure deleted file mode 100755 index c303c884..00000000 --- a/minecode/configure +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/ for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -set -e -#set -x - -################################ -# A configuration script to set things up: -# create a virtualenv and install or update thirdparty packages. -# Source this script for initial configuration -# Use configure --help for details -# -# NOTE: please keep in sync with Windows script configure.bat -# -# This script will search for a virtualenv.pyz app in etc/thirdparty/virtualenv.pyz -# Otherwise it will download the latest from the VIRTUALENV_PYZ_URL default -################################ -CLI_ARGS=$1 - -################################ -# Defaults. Change these variables to customize this script -################################ - -CUSTOM_PACKAGES="https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip" - -# Requirement arguments passed to pip and used by default or with --dev. -REQUIREMENTS="$CUSTOM_PACKAGES --editable ../clearcode-toolkit --editable ../packagedb --editable . --constraint requirements.txt" -DEV_REQUIREMENTS="$CUSTOM_PACKAGES --editable ../clearcode-toolkit --editable ../packagedb[testing] --editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt" -DOCS_REQUIREMENTS="$CUSTOM_PACKAGES --editable ../clearcode-toolkit --editable ../packagedb[docs] --editable .[docs] --constraint requirements.txt" - -# where we create a virtualenv -VIRTUALENV_DIR=venv - -# Cleanable files and directories to delete with the --clean option -CLEANABLE="build venv" - -# extra arguments passed to pip -PIP_EXTRA_ARGS=" " - -# the URL to download virtualenv.pyz if needed -VIRTUALENV_PYZ_URL=https://bootstrap.pypa.io/virtualenv.pyz -################################ - - -################################ -# Current directory where this script lives -CFG_ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -CFG_BIN_DIR=$CFG_ROOT_DIR/$VIRTUALENV_DIR/bin - - -################################ -# Install with or without and index. With "--no-index" this is using only local wheels -# This is an offline mode with no index and no network operations -# NO_INDEX="--no-index " -NO_INDEX="" - - -################################ -# Thirdparty package locations and index handling -# Find packages from the local thirdparty directory if present -THIRDPARDIR=$CFG_ROOT_DIR/thirdparty -if [[ "$(echo $THIRDPARDIR/*.whl)x" != "$THIRDPARDIR/*.whlx" ]]; then - PIP_EXTRA_ARGS="$NO_INDEX --find-links $THIRDPARDIR" -fi - - -################################ -# Set the quiet flag to empty if not defined -if [[ "$CFG_QUIET" == "" ]]; then - CFG_QUIET=" " -fi - - -################################ -# Find a proper Python to run -# Use environment variables or a file if available. -# Otherwise the latest Python by default. -find_python() { - if [[ "$PYTHON_EXECUTABLE" == "" ]]; then - # check for a file named PYTHON_EXECUTABLE - if [ -f "$CFG_ROOT_DIR/PYTHON_EXECUTABLE" ]; then - PYTHON_EXECUTABLE=$(cat "$CFG_ROOT_DIR/PYTHON_EXECUTABLE") - else - PYTHON_EXECUTABLE=python3 - fi - fi -} - - -################################ -create_virtualenv() { - # create a virtualenv for Python - # Note: we do not use the bundled Python 3 "venv" because its behavior and - # presence is not consistent across Linux distro and sometimes pip is not - # included either by default. The virtualenv.pyz app cures all these issues. - - VENV_DIR="$1" - if [ ! -f "$CFG_BIN_DIR/python" ]; then - - mkdir -p "$CFG_ROOT_DIR/$VENV_DIR" - - if [ -f "$CFG_ROOT_DIR/etc/thirdparty/virtualenv.pyz" ]; then - VIRTUALENV_PYZ="$CFG_ROOT_DIR/etc/thirdparty/virtualenv.pyz" - else - VIRTUALENV_PYZ="$CFG_ROOT_DIR/$VENV_DIR/virtualenv.pyz" - wget -O "$VIRTUALENV_PYZ" "$VIRTUALENV_PYZ_URL" 2>/dev/null || curl -o "$VIRTUALENV_PYZ" "$VIRTUALENV_PYZ_URL" - fi - - $PYTHON_EXECUTABLE "$VIRTUALENV_PYZ" \ - --wheel embed --pip embed --setuptools embed \ - --seeder pip \ - --never-download \ - --no-periodic-update \ - --no-vcs-ignore \ - $CFG_QUIET \ - "$CFG_ROOT_DIR/$VENV_DIR" - fi -} - - -################################ -install_packages() { - # install requirements in virtualenv - # note: --no-build-isolation means that pip/wheel/setuptools will not - # be reinstalled a second time and reused from the virtualenv and this - # speeds up the installation. - # We always have the PEP517 build dependencies installed already. - - "$CFG_BIN_DIR/pip" install \ - --upgrade \ - --no-build-isolation \ - $CFG_QUIET \ - $PIP_EXTRA_ARGS \ - $1 -} - - -################################ -cli_help() { - echo An initial configuration script - echo " usage: ./configure [options]" - echo - echo The default is to configure for regular use. Use --dev for development. - echo - echo The options are: - echo " --clean: clean built and installed files and exit." - echo " --dev: configure the environment for development." - echo " --help: display this help message and exit." - echo - echo By default, the python interpreter version found in the path is used. - echo Alternatively, the PYTHON_EXECUTABLE environment variable can be set to - echo configure another Python executable interpreter to use. If this is not - echo set, a file named PYTHON_EXECUTABLE containing a single line with the - echo path of the Python executable to use will be checked last. - set +e - exit -} - - -################################ -clean() { - # Remove cleanable file and directories and files from the root dir. - echo "* Cleaning ..." - for cln in $CLEANABLE; - do rm -rf "${CFG_ROOT_DIR:?}/${cln:?}"; - done - set +e - exit -} - - -################################ -# Main command line entry point -CFG_REQUIREMENTS=$REQUIREMENTS - -# We are using getopts to parse option arguments that start with "-" -while getopts :-: optchar; do - case "${optchar}" in - -) - case "${OPTARG}" in - help ) cli_help;; - clean ) find_python && clean;; - dev ) CFG_REQUIREMENTS="$DEV_REQUIREMENTS";; - docs ) CFG_REQUIREMENTS="$DOCS_REQUIREMENTS";; - esac;; - esac -done - - -PIP_EXTRA_ARGS="$PIP_EXTRA_ARGS" - -find_python -create_virtualenv "$VIRTUALENV_DIR" -install_packages "$CFG_REQUIREMENTS" -. "$CFG_BIN_DIR/activate" - - -set +e diff --git a/minecode/configure.bat b/minecode/configure.bat deleted file mode 100644 index 41547cc5..00000000 --- a/minecode/configure.bat +++ /dev/null @@ -1,207 +0,0 @@ -@echo OFF -@setlocal - -@rem Copyright (c) nexB Inc. and others. All rights reserved. -@rem SPDX-License-Identifier: Apache-2.0 -@rem See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -@rem See https://github.com/nexB/ for support or download. -@rem See https://aboutcode.org for more information about nexB OSS projects. - - -@rem ################################ -@rem # A configuration script to set things up: -@rem # create a virtualenv and install or update thirdparty packages. -@rem # Source this script for initial configuration -@rem # Use configure --help for details - -@rem # NOTE: please keep in sync with POSIX script configure - -@rem # This script will search for a virtualenv.pyz app in etc\thirdparty\virtualenv.pyz -@rem # Otherwise it will download the latest from the VIRTUALENV_PYZ_URL default -@rem ################################ - - -@rem ################################ -@rem # Defaults. Change these variables to customize this script -@rem ################################ - -@rem # Requirement arguments passed to pip and used by default or with --dev. -set "REQUIREMENTS=--editable . --constraint requirements.txt" -set "DEV_REQUIREMENTS=--editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt" -set "DOCS_REQUIREMENTS=--editable .[docs] --constraint requirements.txt" - -@rem # where we create a virtualenv -set "VIRTUALENV_DIR=venv" - -@rem # Cleanable files and directories to delete with the --clean option -set "CLEANABLE=build venv" - -@rem # extra arguments passed to pip -set "PIP_EXTRA_ARGS= " - -@rem # the URL to download virtualenv.pyz if needed -set VIRTUALENV_PYZ_URL=https://bootstrap.pypa.io/virtualenv.pyz -@rem ################################ - - -@rem ################################ -@rem # Current directory where this script lives -set CFG_ROOT_DIR=%~dp0 -set "CFG_BIN_DIR=%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\Scripts" - - -@rem ################################ -@rem # Thirdparty package locations and index handling -@rem # Find packages from the local thirdparty directory -if exist "%CFG_ROOT_DIR%\thirdparty" ( - set PIP_EXTRA_ARGS=--find-links "%CFG_ROOT_DIR%\thirdparty" -) - - -@rem ################################ -@rem # Set the quiet flag to empty if not defined -if not defined CFG_QUIET ( - set "CFG_QUIET= " -) - - -@rem ################################ -@rem # Main command line entry point -set "CFG_REQUIREMENTS=%REQUIREMENTS%" - -:again -if not "%1" == "" ( - if "%1" EQU "--help" (goto cli_help) - if "%1" EQU "--clean" (goto clean) - if "%1" EQU "--dev" ( - set "CFG_REQUIREMENTS=%DEV_REQUIREMENTS%" - ) - if "%1" EQU "--docs" ( - set "CFG_REQUIREMENTS=%DOCS_REQUIREMENTS%" - ) - shift - goto again -) - -set "PIP_EXTRA_ARGS=%PIP_EXTRA_ARGS%" - - -@rem ################################ -@rem # Find a proper Python to run -@rem # Use environment variables or a file if available. -@rem # Otherwise the latest Python by default. -if not defined PYTHON_EXECUTABLE ( - @rem # check for a file named PYTHON_EXECUTABLE - if exist "%CFG_ROOT_DIR%\PYTHON_EXECUTABLE" ( - set /p PYTHON_EXECUTABLE=<"%CFG_ROOT_DIR%\PYTHON_EXECUTABLE" - ) else ( - set "PYTHON_EXECUTABLE=py" - ) -) - - -@rem ################################ -:create_virtualenv -@rem # create a virtualenv for Python -@rem # Note: we do not use the bundled Python 3 "venv" because its behavior and -@rem # presence is not consistent across Linux distro and sometimes pip is not -@rem # included either by default. The virtualenv.pyz app cures all these issues. - -if not exist "%CFG_BIN_DIR%\python.exe" ( - if not exist "%CFG_BIN_DIR%" ( - mkdir "%CFG_BIN_DIR%" - ) - - if exist "%CFG_ROOT_DIR%\etc\thirdparty\virtualenv.pyz" ( - %PYTHON_EXECUTABLE% "%CFG_ROOT_DIR%\etc\thirdparty\virtualenv.pyz" ^ - --wheel embed --pip embed --setuptools embed ^ - --seeder pip ^ - --never-download ^ - --no-periodic-update ^ - --no-vcs-ignore ^ - %CFG_QUIET% ^ - "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%" - ) else ( - if not exist "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\virtualenv.pyz" ( - curl -o "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\virtualenv.pyz" %VIRTUALENV_PYZ_URL% - - if %ERRORLEVEL% neq 0 ( - exit /b %ERRORLEVEL% - ) - ) - %PYTHON_EXECUTABLE% "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\virtualenv.pyz" ^ - --wheel embed --pip embed --setuptools embed ^ - --seeder pip ^ - --never-download ^ - --no-periodic-update ^ - --no-vcs-ignore ^ - %CFG_QUIET% ^ - "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%" - ) -) - -if %ERRORLEVEL% neq 0 ( - exit /b %ERRORLEVEL% -) - - -@rem ################################ -:install_packages -@rem # install requirements in virtualenv -@rem # note: --no-build-isolation means that pip/wheel/setuptools will not -@rem # be reinstalled a second time and reused from the virtualenv and this -@rem # speeds up the installation. -@rem # We always have the PEP517 build dependencies installed already. - -"%CFG_BIN_DIR%\pip" install ^ - --upgrade ^ - --no-build-isolation ^ - %CFG_QUIET% ^ - %PIP_EXTRA_ARGS% ^ - %CFG_REQUIREMENTS% - - -@rem ################################ -:create_bin_junction -@rem # Create junction to bin to have the same directory between linux and windows -if exist "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\bin" ( - rmdir /s /q "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\bin" -) -mklink /J "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\bin" "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\Scripts" - -if %ERRORLEVEL% neq 0 ( - exit /b %ERRORLEVEL% -) - -exit /b 0 - - -@rem ################################ -:cli_help - echo An initial configuration script - echo " usage: configure [options]" - echo " " - echo The default is to configure for regular use. Use --dev for development. - echo " " - echo The options are: - echo " --clean: clean built and installed files and exit." - echo " --dev: configure the environment for development." - echo " --help: display this help message and exit." - echo " " - echo By default, the python interpreter version found in the path is used. - echo Alternatively, the PYTHON_EXECUTABLE environment variable can be set to - echo configure another Python executable interpreter to use. If this is not - echo set, a file named PYTHON_EXECUTABLE containing a single line with the - echo path of the Python executable to use will be checked last. - exit /b 0 - - -@rem ################################ -:clean -@rem # Remove cleanable file and directories and files from the root dir. -echo "* Cleaning ..." -for %%F in (%CLEANABLE%) do ( - rmdir /s /q "%CFG_ROOT_DIR%\%%F" >nul 2>&1 - del /f /q "%CFG_ROOT_DIR%\%%F" >nul 2>&1 -) -exit /b 0 diff --git a/minecode/manage.py b/minecode/manage.py deleted file mode 100755 index ba7386c7..00000000 --- a/minecode/manage.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "minecodeio.settings.dev") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff --git a/minecode/pyproject.toml b/minecode/pyproject.toml deleted file mode 100644 index c43c8a91..00000000 --- a/minecode/pyproject.toml +++ /dev/null @@ -1,47 +0,0 @@ -[build-system] -requires = ["setuptools >= 50", "wheel"] -build-backend = "setuptools.build_meta" - -[tool.pytest.ini_options] -norecursedirs = [ - ".git", - "bin", - "dist", - "build", - "_build", - "dist", - "etc", - "local", - "ci", - "docs", - "man", - "share", - "samples", - ".cache", - ".settings", - "Include", - "include", - "Lib", - "lib", - "lib64", - "Lib64", - "Scripts", - "thirdparty", - "tmp", - "venv", - "tests/data", - ".eggs", - "src/*/data", - "tests/*/data" -] - -python_files = "*.py" - -python_classes = "Test" -python_functions = "test" - -addopts = [ - "-rfExXw", - "--strict-markers", - "--doctest-modules" -] diff --git a/minecode/requirements-dev.txt b/minecode/requirements-dev.txt deleted file mode 100644 index 19627e69..00000000 --- a/minecode/requirements-dev.txt +++ /dev/null @@ -1,15 +0,0 @@ -aboutcode-toolkit==7.0.2 -black==22.10.0 -et-xmlfile==1.1.0 -execnet==1.9.0 -iniconfig==1.1.1 -mock==4.0.3 -mypy-extensions==0.4.3 -openpyxl==3.0.10 -pathspec==0.10.1 -platformdirs==2.5.2 -py==1.11.0 -pytest==7.1.3 -pytest-forked==1.4.0 -pytest-xdist==2.5.0 -tomli==2.0.1 \ No newline at end of file diff --git a/minecode/requirements.txt b/minecode/requirements.txt deleted file mode 100644 index 438c577a..00000000 --- a/minecode/requirements.txt +++ /dev/null @@ -1,96 +0,0 @@ -arrow==1.2.3 -asgiref==3.5.2 -attrs==22.1.0 -banal==1.0.6 -beautifulsoup4==4.11.1 -binaryornot==0.4.4 -boolean.py==4.0 -certifi==2022.9.24 -cffi==1.15.1 -chardet==5.0.0 -charset-normalizer==2.1.1 -click==8.1.3 -colorama==0.4.5 -commoncode==31.0.0 -container-inspector==32.0.1 -cryptography==38.0.1 -debian-inspector==31.0.0 -Deprecated==1.2.13 -Django==4.1.2 -django-filter==22.1 -djangorestframework==3.14.0 -dockerfile-parse==1.2.0 -dparse2==0.6.1 -extractcode==31.0.0 -extractcode-7z==16.5.210531 -extractcode-libarchive==3.5.1.210531 -fasteners==0.18 -fingerprints==1.0.3 -ftfy==6.1.1 -ftputil==5.0.4 -future==0.18.2 -gemfileparser2==0.9.0 -html5lib==1.1 -idna==3.4 -importlib-metadata==5.0.0 -intbitset==3.0.1 -isodate==0.6.1 -jaraco.functools==3.5.2 -javaproperties==0.8.1 -jawa==2.2.0 -Jinja2==3.1.2 -jsonstreams==0.6.0 -license-expression==30.0.0 -lxml==4.9.1 -MarkupSafe==2.1.1 -more-itertools==9.0.0 -natsort==8.2.0 -normality==2.4.0 -packageurl-python==0.10.4 -packaging==21.3 -parameter-expansion-patched==0.3.1 -pdfminer.six==20220524 -pefile==2022.5.30 -pip==22.2.2 -pip-requirements-parser==31.2.0 -pkginfo2==30.0.0 -pluggy==1.0.0 -plugincode==31.0.0 -ply==3.11 -psycopg2==2.9.3 -publicsuffix2==2.20191221 -pyahocorasick==2.0.0b1 -pycparser==2.21 -PyGithub==1.56 -pygmars==0.7.0 -Pygments==2.13.0 -PyJWT==2.6.0 -pymaven-patch==0.3.0 -PyNaCl==1.5.0 -pyparsing==3.0.9 -python-dateutil==2.8.2 -pytz==2022.5 -PyYAML==6.0 -rdflib==6.2.0 -reppy2==0.3.6 -requests==2.28.1 -rubymarshal==1.0.3 -saneyaml==0.5.2 -scancode-toolkit -setuptools==65.3.0 -six==1.16.0 -soupsieve==2.3.2.post1 -spdx-tools==0.7.0rc0 -sqlparse==0.4.3 -text-unidecode==1.3 -toml==0.10.2 -typecode==30.0.0 -typecode-libmagic==5.39.210531 -urllib3==1.26.12 -urlpy==0.5 -wcwidth==0.2.5 -webencodings==0.5.1 -wheel==0.37.1 -wrapt==1.14.1 -xmltodict==0.13.0 -zipp==3.9.0 diff --git a/minecode/setup.cfg b/minecode/setup.cfg deleted file mode 100644 index 201c8274..00000000 --- a/minecode/setup.cfg +++ /dev/null @@ -1,79 +0,0 @@ -[metadata] -name = minecode -version = 2.0.0 - -author = nexB. Inc. and others -author_email = info@nexb.com -license = license = Apache-2.0 AND CC-BY-SA-4.0 - -# description must be on ONE line https://github.com/pypa/setuptools/issues/1390 -description = MineCode - A purl mining tool -long_description = file:README.rst -url = https://github.com/nexB/purldb/minecode -classifiers = - Programming Language :: Python - Programming Language :: Python :: 3 - Topic :: Utilities - -keywords = - packagedb - scancode - purl - purldb - -license_files = - apache-2.0.LICENSE - cc-by-sa-4.0.LICENSE - CHANGELOG.rst - README.rst - AUTHORS.rst - NOTICE - - -[options] -#setup_requires = setuptools_scm[toml] >= 4 - -package_dir = - =src - -packages = find: -include_package_data = true -zip_safe = false -install_requires = - arrow==1.2.3 - clearcode-toolkit==0.0.3 - debian-inspector==31.0.0 - Django==4.1.2 - django-filter==22.1 - djangorestframework==3.14.0 - ftputil==5.0.4 - jawa==2.2.0 - packagedb==2.0.0 - psycopg2-binary==2.9.3 - PyGithub==1.56 - rubymarshal==1.0.3 - scancode-toolkit - reppy2==0.3.6 - urlpy==0.5 - -python_requires = >=3.8.* - - -[options.packages.find] -where = src - - -[options.extras_require] -testing = - pytest >= 6, != 7.0.0 - pytest-xdist >= 2 - pytest-django - aboutcode-toolkit >= 6.0.0 - black - mock - - -docs = - Sphinx >= 3.3.1 - sphinx-rtd-theme >= 0.5.0 - doc8 >= 0.8.1 diff --git a/minecode/setup.py b/minecode/setup.py deleted file mode 100644 index bac24a43..00000000 --- a/minecode/setup.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python - -import setuptools - -if __name__ == "__main__": - setuptools.setup() diff --git a/minecode/src/minecodeio/settings/__init__.py b/minecode/src/minecodeio/settings/__init__.py deleted file mode 100644 index 060956c0..00000000 --- a/minecode/src/minecodeio/settings/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -import socket - - -PRODUCTION_HOSTNAME = 'TBD' - -hostname = socket.gethostname() - -if hostname.endswith(PRODUCTION_HOSTNAME): - from minecodeio.settings.production import * -else: - from minecodeio.settings.dev import * - -# DO NOT ADD ANYTHING MORE HERE diff --git a/minecode/src/minecodeio/settings/base.py b/minecode/src/minecodeio/settings/base.py deleted file mode 100644 index 47ce4ad2..00000000 --- a/minecode/src/minecodeio/settings/base.py +++ /dev/null @@ -1,157 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - - -import os -from os.path import join, dirname, abspath - -""" -Django settings for minecodeio project. -""" - - -here = lambda *dirs: join(abspath(dirname(__file__)), *dirs) -BASE_DIR = here('..', '..') -root = lambda *dirs: join(abspath(BASE_DIR), *dirs) -root_parent = lambda *dirs: join(abspath(root('..')), *dirs) - - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(__file__)) - - -INSTALLED_APPS = ( - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'rest_framework', - 'django_filters', - 'discovery', - 'clearindex', - 'packagedb', - 'clearcode', -) - -MIDDLEWARE = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) - -ROOT_URLCONF = 'minecodeio.urls' - -# Database -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'packagedb', - 'USER': 'packagedb', - 'PASSWORD': 'packagedb', - 'HOST': 'localhost', - 'PORT': '5432', - 'ATOMIC_REQUESTS': True, - } -} - -REST_FRAMEWORK = { - 'DEFAULT_PAGINATION_CLASS': 'packagedb.api_custom.PageSizePagination', - 'DEFAULT_AUTHENTICATION_CLASSES': (), - 'DEFAULT_RENDERER_CLASSES': ( - 'rest_framework.renderers.BrowsableAPIRenderer', - 'rest_framework.renderers.JSONRenderer', - 'rest_framework.renderers.AdminRenderer', - ), - 'DEFAULT_FILTER_BACKENDS': ( - 'django_filters.rest_framework.DjangoFilterBackend', - 'rest_framework.filters.SearchFilter', - ), -} - -# Internationalization - -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'US/Pacific' - -USE_I18N = True - -USE_L10N = True - -USE_TZ = True - -# Static files (CSS, JavaScript, Images) - -STATIC_URL = '/static/' - -# Templates -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - # The cached template loader is a class-based loader that you - # configure with a list of other loaders that it should wrap. - # The wrapped loaders are used to locate unknown templates when - # they are first encountered. The cached loader then stores the - # compiled Template in memory. The cached Template instance is - # returned for subsequent requests to load the same template. - 'loaders': [ - ('django.template.loaders.cached.Loader', [ - 'django.template.loaders.app_directories.Loader', - ]), - ], - }, - }, -] - -# Cache - -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - } -} - -# Security - -CSRF_COOKIE_SECURE = True - -SESSION_COOKIE_SECURE = True - -# Requests - -REQUESTS_ARGS = { - - # Configuration for Request to use Tor over a Privoxy proxy - # Comment-out for a direct access - # 'proxies': { - # 'http': 'http://127.0.0.1:8118', - # 'https': 'https://127.0.0.1:8118', - # } -} - -# Instead of sending out real emails the console backend just writes the emails -# that would be sent to the standard output. -EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' - -# Active seeders: each active seeder class need to be added explictly here -ACTIVE_SEEDERS = [ - 'discovery.visitors.npm.NpmSeed', -] diff --git a/minecode/src/minecodeio/settings/ci.py b/minecode/src/minecodeio/settings/ci.py deleted file mode 100644 index 715eaa21..00000000 --- a/minecode/src/minecodeio/settings/ci.py +++ /dev/null @@ -1,28 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - - -from os.path import join, dirname, abspath - -""" -Django settings for continuous integration -""" - -# Using exec() instead of "import *" to avoid any side effects -with open(join(dirname(abspath(__file__)), 'base.py')) as f: - exec(f.read()) - - -# Reset the custom Requests args in test mode -REQUESTS_ARGS = {} - -DATABASES['default']['USER'] = 'postgres' -DATABASES['default']['PASSWORD'] = 'postgres' - -SECRET_KEY = '-@j@p68cdzzxss3x8=i#*ml#@-k3$l=b7_cd440$36pn)mddam' diff --git a/minecode/src/minecodeio/settings/dev.py b/minecode/src/minecodeio/settings/dev.py deleted file mode 100644 index e4af7cd4..00000000 --- a/minecode/src/minecodeio/settings/dev.py +++ /dev/null @@ -1,27 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - - -from os.path import join, dirname, abspath - -""" -Django settings for local development -""" - -# Using exec() instead of "import *" to avoid any side effects -with open(join(dirname(abspath(__file__)), 'base.py')) as f: - exec(f.read()) - - -DEBUG = True -TEMPLATES[0]['OPTIONS']['debug'] = True - -SECRET_KEY = '-@j@p68cdzzk2s3x8=i#*ml#@-k3$l=b7_cd440$36pn)msdam' - -EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' diff --git a/minecode/src/minecodeio/urls.py b/minecode/src/minecodeio/urls.py deleted file mode 100644 index 4323d5d7..00000000 --- a/minecode/src/minecodeio/urls.py +++ /dev/null @@ -1,19 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - - -from django.conf.urls import include -from django.urls import re_path - -from packagedbio import urls as packagedb_urls - - -urlpatterns = [ - re_path(r'^api/', include((packagedb_urls.api_router.urls, 'api'))), -] diff --git a/minecode/src/minecodeio/wsgi.py b/minecode/src/minecodeio/wsgi.py deleted file mode 100644 index 080a2c69..00000000 --- a/minecode/src/minecodeio/wsgi.py +++ /dev/null @@ -1,27 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - - -import os -from django.core.wsgi import get_wsgi_application - - -""" -WSGI config for minecodeio_site project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ -""" - - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "minecodeio.settings") - -application = get_wsgi_application() diff --git a/minecode/tests/discovery/__init__.py b/minecode/tests/discovery/__init__.py deleted file mode 100644 index 2eb8f9f0..00000000 --- a/minecode/tests/discovery/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# diff --git a/minecode/tests/test_skeleton_codestyle.py b/minecode/tests/test_skeleton_codestyle.py deleted file mode 100644 index 2eb6e558..00000000 --- a/minecode/tests/test_skeleton_codestyle.py +++ /dev/null @@ -1,36 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# ScanCode is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/skeleton for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -import subprocess -import unittest -import configparser - - -class BaseTests(unittest.TestCase): - def test_skeleton_codestyle(self): - """ - This test shouldn't run in proliferated repositories. - """ - setup_cfg = configparser.ConfigParser() - setup_cfg.read("setup.cfg") - if setup_cfg["metadata"]["name"] != "skeleton": - return - - args = "venv/bin/black --check -l 100 setup.py etc tests" - try: - subprocess.check_output(args.split()) - except subprocess.CalledProcessError as e: - print("===========================================================") - print(e.output) - print("===========================================================") - raise Exception( - "Black style check failed; please format the code using:\n" - " python -m black -l 100 setup.py etc tests", - e.output, - ) from e diff --git a/packagedb/.gitattributes b/packagedb/.gitattributes deleted file mode 100644 index 96c89ceb..00000000 --- a/packagedb/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -# Ignore all Git auto CR/LF line endings conversions -* -text -pyproject.toml export-subst diff --git a/packagedb/.gitignore b/packagedb/.gitignore deleted file mode 100644 index 4c2316fc..00000000 --- a/packagedb/.gitignore +++ /dev/null @@ -1,74 +0,0 @@ -# Python compiled files -*.py[cod] - -# virtualenv and other misc bits -*.egg-info -/dist -/build -/bin -/lib -/scripts -/Scripts -/Lib -/pip-selfcheck.json -/tmp -/venv -.Python -/include -/Include -/local -*/local/* -/local/ -/share/ -/tcl/ -/.eggs/ - -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.cache -.coverage -.coverage.* -nosetests.xml -htmlcov - -# Translations -*.mo - -# IDEs -.project -.pydevproject -.idea -org.eclipse.core.resources.prefs -.vscode -.vs - -# Sphinx -docs/_build -docs/bin -docs/build -docs/include -docs/Lib -doc/pyvenv.cfg -pyvenv.cfg - -# Various junk and temp files -.DS_Store -*~ -.*.sw[po] -.build -.ve -*.bak -/.cache/ - -# pyenv -/.python-version -/man/ -/.pytest_cache/ -lib64 -tcl - -# Ignore Jupyter Notebook related temp files -.ipynb_checkpoints/ -/.env diff --git a/packagedb/AUTHORS.rst b/packagedb/AUTHORS.rst deleted file mode 100644 index 58c0afe6..00000000 --- a/packagedb/AUTHORS.rst +++ /dev/null @@ -1,7 +0,0 @@ -The following organizations or individuals have contributed to this repo: - -- nexB Inc. -- Jono Yang @JonoYang -- Philippe Ombredanne -- Li Ha -- Steven Esser @majurg \ No newline at end of file diff --git a/packagedb/CHANGELOG.rst b/packagedb/CHANGELOG.rst deleted file mode 100644 index 7c378e5d..00000000 --- a/packagedb/CHANGELOG.rst +++ /dev/null @@ -1,10 +0,0 @@ - -Release notes -============= - - - -Version v1.0.0 ----------------- - -Initial release \ No newline at end of file diff --git a/packagedb/MANIFEST.in b/packagedb/MANIFEST.in deleted file mode 100644 index 613dbab5..00000000 --- a/packagedb/MANIFEST.in +++ /dev/null @@ -1,21 +0,0 @@ -graft etc -graft src -graft tests - -include *.LICENSE -include NOTICE -include *.ABOUT -include *.toml -include *.yml -include *.rst -include setup.* -include configure* -include requirements* -include .git* -include MANIFEST.in -include setup.cfg -include setup.py - - -global-exclude *.py[co] __pycache__ *.*~ - diff --git a/packagedb/Makefile b/packagedb/Makefile deleted file mode 100644 index 475e034c..00000000 --- a/packagedb/Makefile +++ /dev/null @@ -1,117 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -# Python version can be specified with `$ PYTHON_EXE=python3.x make conf` -PYTHON_EXE?=python3 -VENV=venv -MANAGE=${VENV}/bin/python manage.py -ACTIVATE?=. ${VENV}/bin/activate; -VIRTUALENV_PYZ=../etc/thirdparty/virtualenv.pyz -# Do not depend on Python to generate the SECRET_KEY -GET_SECRET_KEY=`base64 /dev/urandom | head -c50` -# Customize with `$ make envfile ENV_FILE=/etc/purldb/.env` -ENV_FILE=.env -# Customize with `$ make postgres PACKAGEDB_DB_PASSWORD=YOUR_PASSWORD` -PACKAGEDB_DB_PASSWORD=packagedb - -# Use sudo for postgres, but only on Linux -UNAME := $(shell uname) -ifeq ($(UNAME), Linux) - SUDO_POSTGRES=sudo -u postgres -else - SUDO_POSTGRES= -endif - -virtualenv: - @echo "-> Bootstrap the virtualenv with PYTHON_EXE=${PYTHON_EXE}" - @${PYTHON_EXE} ${VIRTUALENV_PYZ} --never-download --no-periodic-update ${VENV} - -conf: - @echo "-> Install dependencies" - @./configure - -dev: - @echo "-> Configure and install development dependencies" - @./configure --dev - -envfile: - @echo "-> Create the .env file and generate a secret key" - @if test -f ${ENV_FILE}; then echo ".env file exists already"; exit 1; fi - @mkdir -p $(shell dirname ${ENV_FILE}) && touch ${ENV_FILE} - @echo SECRET_KEY=\"${GET_SECRET_KEY}\" > ${ENV_FILE} - -isort: - @echo "-> Apply isort changes to ensure proper imports ordering" - ${VENV}/bin/isort . - -black: - @echo "-> Apply black code formatter" - ${VENV}/bin/black . - -doc8: - @echo "-> Run doc8 validation" - @${ACTIVATE} doc8 --max-line-length 100 --ignore-path docs/_build/ --quiet docs/ - -valid: isort black - -check: - @echo "-> Run pycodestyle (PEP8) validation" - @${ACTIVATE} pycodestyle --max-line-length=100 --exclude=venv,lib,thirdparty,docs,migrations,settings.py . - @echo "-> Run isort imports ordering validation" - @${ACTIVATE} isort --check-only . - @echo "-> Run black validation" - @${ACTIVATE} black --check ${BLACK_ARGS} - -clean: - @echo "-> Clean the Python env" - @./configure --clean - -migrate: - @echo "-> Apply database migrations" - ${MANAGE} migrate - -postgres: - @echo "-> Configure PostgreSQL database" - @echo "-> Create database user 'packagedb'" - ${SUDO_POSTGRES} createuser --no-createrole --no-superuser --login --inherit --createdb packagedb || true - ${SUDO_POSTGRES} psql -c "alter user packagedb with encrypted password '${PACKAGEDB_DB_PASSWORD}';" || true - @echo "-> Drop 'packagedb' database" - ${SUDO_POSTGRES} dropdb packagedb || true - @echo "-> Create 'packagedb' database" - ${SUDO_POSTGRES} createdb --encoding=utf-8 --owner=packagedb packagedb - @$(MAKE) migrate - -run: - ${MANAGE} runserver 8001 --insecure - -test: - @echo "-> Run the test suite" - ${ACTIVATE} DJANGO_SETTINGS_MODULE=packagedbio.settings ${PYTHON_EXE} -m pytest -vvs - -shell: - ${MANAGE} shell - -bump: - @echo "-> Bump the version" - bin/bumpver update --no-fetch --patch - -docs: - rm -rf docs/_build/ - @${ACTIVATE} sphinx-build docs/ docs/_build/ - -docker-images: - @echo "-> Build Docker services" - docker-compose build - @echo "-> Pull service images" - docker-compose pull - @echo "-> Save the service images to a compressed tar archive in the dist/ directory" - @mkdir -p dist/ - @docker save postgres packagedb_packagedb nginx | gzip > dist/packagedb-images-`git describe --tags`.tar.gz - -.PHONY: virtualenv conf dev envfile isort black doc8 valid check clean migrate postgres run test shell bump docs docker-images diff --git a/packagedb/NOTICE b/packagedb/NOTICE deleted file mode 100644 index dab28052..00000000 --- a/packagedb/NOTICE +++ /dev/null @@ -1,12 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 AND CC-BY-SA-4.0 -# purldb software is licensed under the Apache License version 2.0. -# purldb data is licensed collectively under CC-BY-SA-4.0. -# See https://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://creativecommons.org/licenses/by-sa/4.0/legalcode for the license text. -# -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# diff --git a/packagedb/README.rst b/packagedb/README.rst deleted file mode 100644 index 1796724b..00000000 --- a/packagedb/README.rst +++ /dev/null @@ -1,12 +0,0 @@ -packagedb -========= - -PackageDB is a Django web app that provides the model for storing package metadata. - -Local setup -=========== - -* ./configure -* make postgres -* make envfile -* make run diff --git a/minecode/tests/__init__.py b/packagedb/__init__.py similarity index 100% rename from minecode/tests/__init__.py rename to packagedb/__init__.py diff --git a/packagedb/apache-2.0.LICENSE b/packagedb/apache-2.0.LICENSE deleted file mode 100644 index 261eeb9e..00000000 --- a/packagedb/apache-2.0.LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packagedb/src/packagedb/api.py b/packagedb/api.py similarity index 100% rename from packagedb/src/packagedb/api.py rename to packagedb/api.py diff --git a/matchcode/src/matchcode/api_custom.py b/packagedb/api_custom.py similarity index 100% rename from matchcode/src/matchcode/api_custom.py rename to packagedb/api_custom.py diff --git a/packagedb/cc-by-sa-4.0.LICENSE b/packagedb/cc-by-sa-4.0.LICENSE deleted file mode 100644 index e04b480f..00000000 --- a/packagedb/cc-by-sa-4.0.LICENSE +++ /dev/null @@ -1,427 +0,0 @@ -Attribution-ShareAlike 4.0 International - -======================================================================= - -Creative Commons Corporation ("Creative Commons") is not a law firm and -does not provide legal services or legal advice. Distribution of -Creative Commons public licenses does not create a lawyer-client or -other relationship. Creative Commons makes its licenses and related -information available on an "as-is" basis. Creative Commons gives no -warranties regarding its licenses, any material licensed under their -terms and conditions, or any related information. Creative Commons -disclaims all liability for damages resulting from their use to the -fullest extent possible. - -Using Creative Commons Public Licenses - -Creative Commons public licenses provide a standard set of terms and -conditions that creators and other rights holders may use to share -original works of authorship and other material subject to copyright -and certain other rights specified in the public license below. The -following considerations are for informational purposes only, are not -exhaustive, and do not form part of our licenses. - - Considerations for licensors: Our public licenses are - intended for use by those authorized to give the public - permission to use material in ways otherwise restricted by - copyright and certain other rights. Our licenses are - irrevocable. Licensors should read and understand the terms - and conditions of the license they choose before applying it. - Licensors should also secure all rights necessary before - applying our licenses so that the public can reuse the - material as expected. Licensors should clearly mark any - material not subject to the license. This includes other CC- - licensed material, or material used under an exception or - limitation to copyright. More considerations for licensors: - wiki.creativecommons.org/Considerations_for_licensors - - Considerations for the public: By using one of our public - licenses, a licensor grants the public permission to use the - licensed material under specified terms and conditions. If - the licensor's permission is not necessary for any reason--for - example, because of any applicable exception or limitation to - copyright--then that use is not regulated by the license. Our - licenses grant only permissions under copyright and certain - other rights that a licensor has authority to grant. Use of - the licensed material may still be restricted for other - reasons, including because others have copyright or other - rights in the material. A licensor may make special requests, - such as asking that all changes be marked or described. - Although not required by our licenses, you are encouraged to - respect those requests where reasonable. More considerations - for the public: - wiki.creativecommons.org/Considerations_for_licensees - -======================================================================= - -Creative Commons Attribution-ShareAlike 4.0 International Public -License - -By exercising the Licensed Rights (defined below), You accept and agree -to be bound by the terms and conditions of this Creative Commons -Attribution-ShareAlike 4.0 International Public License ("Public -License"). To the extent this Public License may be interpreted as a -contract, You are granted the Licensed Rights in consideration of Your -acceptance of these terms and conditions, and the Licensor grants You -such rights in consideration of benefits the Licensor receives from -making the Licensed Material available under these terms and -conditions. - - -Section 1 -- Definitions. - - a. Adapted Material means material subject to Copyright and Similar - Rights that is derived from or based upon the Licensed Material - and in which the Licensed Material is translated, altered, - arranged, transformed, or otherwise modified in a manner requiring - permission under the Copyright and Similar Rights held by the - Licensor. For purposes of this Public License, where the Licensed - Material is a musical work, performance, or sound recording, - Adapted Material is always produced where the Licensed Material is - synched in timed relation with a moving image. - - b. Adapter's License means the license You apply to Your Copyright - and Similar Rights in Your contributions to Adapted Material in - accordance with the terms and conditions of this Public License. - - c. BY-SA Compatible License means a license listed at - creativecommons.org/compatiblelicenses, approved by Creative - Commons as essentially the equivalent of this Public License. - - d. Copyright and Similar Rights means copyright and/or similar rights - closely related to copyright including, without limitation, - performance, broadcast, sound recording, and Sui Generis Database - Rights, without regard to how the rights are labeled or - categorized. For purposes of this Public License, the rights - specified in Section 2(b)(1)-(2) are not Copyright and Similar - Rights. - - e. Effective Technological Measures means those measures that, in the - absence of proper authority, may not be circumvented under laws - fulfilling obligations under Article 11 of the WIPO Copyright - Treaty adopted on December 20, 1996, and/or similar international - agreements. - - f. Exceptions and Limitations means fair use, fair dealing, and/or - any other exception or limitation to Copyright and Similar Rights - that applies to Your use of the Licensed Material. - - g. License Elements means the license attributes listed in the name - of a Creative Commons Public License. The License Elements of this - Public License are Attribution and ShareAlike. - - h. Licensed Material means the artistic or literary work, database, - or other material to which the Licensor applied this Public - License. - - i. Licensed Rights means the rights granted to You subject to the - terms and conditions of this Public License, which are limited to - all Copyright and Similar Rights that apply to Your use of the - Licensed Material and that the Licensor has authority to license. - - j. Licensor means the individual(s) or entity(ies) granting rights - under this Public License. - - k. Share means to provide material to the public by any means or - process that requires permission under the Licensed Rights, such - as reproduction, public display, public performance, distribution, - dissemination, communication, or importation, and to make material - available to the public including in ways that members of the - public may access the material from a place and at a time - individually chosen by them. - - l. Sui Generis Database Rights means rights other than copyright - resulting from Directive 96/9/EC of the European Parliament and of - the Council of 11 March 1996 on the legal protection of databases, - as amended and/or succeeded, as well as other essentially - equivalent rights anywhere in the world. - - m. You means the individual or entity exercising the Licensed Rights - under this Public License. Your has a corresponding meaning. - - -Section 2 -- Scope. - - a. License grant. - - 1. Subject to the terms and conditions of this Public License, - the Licensor hereby grants You a worldwide, royalty-free, - non-sublicensable, non-exclusive, irrevocable license to - exercise the Licensed Rights in the Licensed Material to: - - a. reproduce and Share the Licensed Material, in whole or - in part; and - - b. produce, reproduce, and Share Adapted Material. - - 2. Exceptions and Limitations. For the avoidance of doubt, where - Exceptions and Limitations apply to Your use, this Public - License does not apply, and You do not need to comply with - its terms and conditions. - - 3. Term. The term of this Public License is specified in Section - 6(a). - - 4. Media and formats; technical modifications allowed. The - Licensor authorizes You to exercise the Licensed Rights in - all media and formats whether now known or hereafter created, - and to make technical modifications necessary to do so. The - Licensor waives and/or agrees not to assert any right or - authority to forbid You from making technical modifications - necessary to exercise the Licensed Rights, including - technical modifications necessary to circumvent Effective - Technological Measures. For purposes of this Public License, - simply making modifications authorized by this Section 2(a) - (4) never produces Adapted Material. - - 5. Downstream recipients. - - a. Offer from the Licensor -- Licensed Material. Every - recipient of the Licensed Material automatically - receives an offer from the Licensor to exercise the - Licensed Rights under the terms and conditions of this - Public License. - - b. Additional offer from the Licensor -- Adapted Material. - Every recipient of Adapted Material from You - automatically receives an offer from the Licensor to - exercise the Licensed Rights in the Adapted Material - under the conditions of the Adapter's License You apply. - - c. No downstream restrictions. You may not offer or impose - any additional or different terms or conditions on, or - apply any Effective Technological Measures to, the - Licensed Material if doing so restricts exercise of the - Licensed Rights by any recipient of the Licensed - Material. - - 6. No endorsement. Nothing in this Public License constitutes or - may be construed as permission to assert or imply that You - are, or that Your use of the Licensed Material is, connected - with, or sponsored, endorsed, or granted official status by, - the Licensor or others designated to receive attribution as - provided in Section 3(a)(1)(A)(i). - - b. Other rights. - - 1. Moral rights, such as the right of integrity, are not - licensed under this Public License, nor are publicity, - privacy, and/or other similar personality rights; however, to - the extent possible, the Licensor waives and/or agrees not to - assert any such rights held by the Licensor to the limited - extent necessary to allow You to exercise the Licensed - Rights, but not otherwise. - - 2. Patent and trademark rights are not licensed under this - Public License. - - 3. To the extent possible, the Licensor waives any right to - collect royalties from You for the exercise of the Licensed - Rights, whether directly or through a collecting society - under any voluntary or waivable statutory or compulsory - licensing scheme. In all other cases the Licensor expressly - reserves any right to collect such royalties. - - -Section 3 -- License Conditions. - -Your exercise of the Licensed Rights is expressly made subject to the -following conditions. - - a. Attribution. - - 1. If You Share the Licensed Material (including in modified - form), You must: - - a. retain the following if it is supplied by the Licensor - with the Licensed Material: - - i. identification of the creator(s) of the Licensed - Material and any others designated to receive - attribution, in any reasonable manner requested by - the Licensor (including by pseudonym if - designated); - - ii. a copyright notice; - - iii. a notice that refers to this Public License; - - iv. a notice that refers to the disclaimer of - warranties; - - v. a URI or hyperlink to the Licensed Material to the - extent reasonably practicable; - - b. indicate if You modified the Licensed Material and - retain an indication of any previous modifications; and - - c. indicate the Licensed Material is licensed under this - Public License, and include the text of, or the URI or - hyperlink to, this Public License. - - 2. You may satisfy the conditions in Section 3(a)(1) in any - reasonable manner based on the medium, means, and context in - which You Share the Licensed Material. For example, it may be - reasonable to satisfy the conditions by providing a URI or - hyperlink to a resource that includes the required - information. - - 3. If requested by the Licensor, You must remove any of the - information required by Section 3(a)(1)(A) to the extent - reasonably practicable. - - b. ShareAlike. - - In addition to the conditions in Section 3(a), if You Share - Adapted Material You produce, the following conditions also apply. - - 1. The Adapter's License You apply must be a Creative Commons - license with the same License Elements, this version or - later, or a BY-SA Compatible License. - - 2. You must include the text of, or the URI or hyperlink to, the - Adapter's License You apply. You may satisfy this condition - in any reasonable manner based on the medium, means, and - context in which You Share Adapted Material. - - 3. You may not offer or impose any additional or different terms - or conditions on, or apply any Effective Technological - Measures to, Adapted Material that restrict exercise of the - rights granted under the Adapter's License You apply. - - -Section 4 -- Sui Generis Database Rights. - -Where the Licensed Rights include Sui Generis Database Rights that -apply to Your use of the Licensed Material: - - a. for the avoidance of doubt, Section 2(a)(1) grants You the right - to extract, reuse, reproduce, and Share all or a substantial - portion of the contents of the database; - - b. if You include all or a substantial portion of the database - contents in a database in which You have Sui Generis Database - Rights, then the database in which You have Sui Generis Database - Rights (but not its individual contents) is Adapted Material, - - including for purposes of Section 3(b); and - c. You must comply with the conditions in Section 3(a) if You Share - all or a substantial portion of the contents of the database. - -For the avoidance of doubt, this Section 4 supplements and does not -replace Your obligations under this Public License where the Licensed -Rights include other Copyright and Similar Rights. - - -Section 5 -- Disclaimer of Warranties and Limitation of Liability. - - a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE - EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS - AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF - ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, - IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, - WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR - PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, - ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT - KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT - ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. - - b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE - TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, - NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, - INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, - COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR - USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN - ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR - DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR - IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. - - c. The disclaimer of warranties and limitation of liability provided - above shall be interpreted in a manner that, to the extent - possible, most closely approximates an absolute disclaimer and - waiver of all liability. - - -Section 6 -- Term and Termination. - - a. This Public License applies for the term of the Copyright and - Similar Rights licensed here. However, if You fail to comply with - this Public License, then Your rights under this Public License - terminate automatically. - - b. Where Your right to use the Licensed Material has terminated under - Section 6(a), it reinstates: - - 1. automatically as of the date the violation is cured, provided - it is cured within 30 days of Your discovery of the - violation; or - - 2. upon express reinstatement by the Licensor. - - For the avoidance of doubt, this Section 6(b) does not affect any - right the Licensor may have to seek remedies for Your violations - of this Public License. - - c. For the avoidance of doubt, the Licensor may also offer the - Licensed Material under separate terms or conditions or stop - distributing the Licensed Material at any time; however, doing so - will not terminate this Public License. - - d. Sections 1, 5, 6, 7, and 8 survive termination of this Public - License. - - -Section 7 -- Other Terms and Conditions. - - a. The Licensor shall not be bound by any additional or different - terms or conditions communicated by You unless expressly agreed. - - b. Any arrangements, understandings, or agreements regarding the - Licensed Material not stated herein are separate from and - independent of the terms and conditions of this Public License. - - -Section 8 -- Interpretation. - - a. For the avoidance of doubt, this Public License does not, and - shall not be interpreted to, reduce, limit, restrict, or impose - conditions on any use of the Licensed Material that could lawfully - be made without permission under this Public License. - - b. To the extent possible, if any provision of this Public License is - deemed unenforceable, it shall be automatically reformed to the - minimum extent necessary to make it enforceable. If the provision - cannot be reformed, it shall be severed from this Public License - without affecting the enforceability of the remaining terms and - conditions. - - c. No term or condition of this Public License will be waived and no - failure to comply consented to unless expressly agreed to by the - Licensor. - - d. Nothing in this Public License constitutes or may be interpreted - as a limitation upon, or waiver of, any privileges and immunities - that apply to the Licensor or You, including from the legal - processes of any jurisdiction or authority. - - -======================================================================= - -Creative Commons is not a party to its public -licenses. Notwithstanding, Creative Commons may elect to apply one of -its public licenses to material it publishes and in those instances -will be considered the “Licensor.” The text of the Creative Commons -public licenses is dedicated to the public domain under the CC0 Public -Domain Dedication. Except for the limited purpose of indicating that -material is shared under a Creative Commons public license or as -otherwise permitted by the Creative Commons policies published at -creativecommons.org/policies, Creative Commons does not authorize the -use of the trademark "Creative Commons" or any other trademark or logo -of Creative Commons without its prior written consent including, -without limitation, in connection with any unauthorized modifications -to any of its public licenses or any other arrangements, -understandings, or agreements concerning use of licensed material. For -the avoidance of doubt, this paragraph does not form part of the -public licenses. - -Creative Commons may be contacted at creativecommons.org. diff --git a/packagedb/configure b/packagedb/configure deleted file mode 100755 index 32e02f55..00000000 --- a/packagedb/configure +++ /dev/null @@ -1,202 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/ for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -set -e -#set -x - -################################ -# A configuration script to set things up: -# create a virtualenv and install or update thirdparty packages. -# Source this script for initial configuration -# Use configure --help for details -# -# NOTE: please keep in sync with Windows script configure.bat -# -# This script will search for a virtualenv.pyz app in etc/thirdparty/virtualenv.pyz -# Otherwise it will download the latest from the VIRTUALENV_PYZ_URL default -################################ -CLI_ARGS=$1 - -################################ -# Defaults. Change these variables to customize this script -################################ - -# Requirement arguments passed to pip and used by default or with --dev. -REQUIREMENTS="--editable . --constraint requirements.txt" -DEV_REQUIREMENTS="--editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt" -DOCS_REQUIREMENTS="--editable .[docs] --constraint requirements.txt" - -# where we create a virtualenv -VIRTUALENV_DIR=venv - -# Cleanable files and directories to delete with the --clean option -CLEANABLE="build venv" - -# extra arguments passed to pip -PIP_EXTRA_ARGS=" " - -# the URL to download virtualenv.pyz if needed -VIRTUALENV_PYZ_URL=https://bootstrap.pypa.io/virtualenv.pyz -################################ - - -################################ -# Current directory where this script lives -CFG_ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -CFG_BIN_DIR=$CFG_ROOT_DIR/$VIRTUALENV_DIR/bin - - -################################ -# Install with or without and index. With "--no-index" this is using only local wheels -# This is an offline mode with no index and no network operations -# NO_INDEX="--no-index " -NO_INDEX="" - - -################################ -# Thirdparty package locations and index handling -# Find packages from the local thirdparty directory if present -THIRDPARDIR=$CFG_ROOT_DIR/thirdparty -if [[ "$(echo $THIRDPARDIR/*.whl)x" != "$THIRDPARDIR/*.whlx" ]]; then - PIP_EXTRA_ARGS="$NO_INDEX --find-links $THIRDPARDIR" -fi - - -################################ -# Set the quiet flag to empty if not defined -if [[ "$CFG_QUIET" == "" ]]; then - CFG_QUIET=" " -fi - - -################################ -# Find a proper Python to run -# Use environment variables or a file if available. -# Otherwise the latest Python by default. -find_python() { - if [[ "$PYTHON_EXECUTABLE" == "" ]]; then - # check for a file named PYTHON_EXECUTABLE - if [ -f "$CFG_ROOT_DIR/PYTHON_EXECUTABLE" ]; then - PYTHON_EXECUTABLE=$(cat "$CFG_ROOT_DIR/PYTHON_EXECUTABLE") - else - PYTHON_EXECUTABLE=python3 - fi - fi -} - - -################################ -create_virtualenv() { - # create a virtualenv for Python - # Note: we do not use the bundled Python 3 "venv" because its behavior and - # presence is not consistent across Linux distro and sometimes pip is not - # included either by default. The virtualenv.pyz app cures all these issues. - - VENV_DIR="$1" - if [ ! -f "$CFG_BIN_DIR/python" ]; then - - mkdir -p "$CFG_ROOT_DIR/$VENV_DIR" - - if [ -f "$CFG_ROOT_DIR/etc/thirdparty/virtualenv.pyz" ]; then - VIRTUALENV_PYZ="$CFG_ROOT_DIR/etc/thirdparty/virtualenv.pyz" - else - VIRTUALENV_PYZ="$CFG_ROOT_DIR/$VENV_DIR/virtualenv.pyz" - wget -O "$VIRTUALENV_PYZ" "$VIRTUALENV_PYZ_URL" 2>/dev/null || curl -o "$VIRTUALENV_PYZ" "$VIRTUALENV_PYZ_URL" - fi - - $PYTHON_EXECUTABLE "$VIRTUALENV_PYZ" \ - --wheel embed --pip embed --setuptools embed \ - --seeder pip \ - --never-download \ - --no-periodic-update \ - --no-vcs-ignore \ - $CFG_QUIET \ - "$CFG_ROOT_DIR/$VENV_DIR" - fi -} - - -################################ -install_packages() { - # install requirements in virtualenv - # note: --no-build-isolation means that pip/wheel/setuptools will not - # be reinstalled a second time and reused from the virtualenv and this - # speeds up the installation. - # We always have the PEP517 build dependencies installed already. - - "$CFG_BIN_DIR/pip" install \ - --upgrade \ - --no-build-isolation \ - $CFG_QUIET \ - $PIP_EXTRA_ARGS \ - $1 -} - - -################################ -cli_help() { - echo An initial configuration script - echo " usage: ./configure [options]" - echo - echo The default is to configure for regular use. Use --dev for development. - echo - echo The options are: - echo " --clean: clean built and installed files and exit." - echo " --dev: configure the environment for development." - echo " --help: display this help message and exit." - echo - echo By default, the python interpreter version found in the path is used. - echo Alternatively, the PYTHON_EXECUTABLE environment variable can be set to - echo configure another Python executable interpreter to use. If this is not - echo set, a file named PYTHON_EXECUTABLE containing a single line with the - echo path of the Python executable to use will be checked last. - set +e - exit -} - - -################################ -clean() { - # Remove cleanable file and directories and files from the root dir. - echo "* Cleaning ..." - for cln in $CLEANABLE; - do rm -rf "${CFG_ROOT_DIR:?}/${cln:?}"; - done - set +e - exit -} - - -################################ -# Main command line entry point -CFG_REQUIREMENTS=$REQUIREMENTS - -# We are using getopts to parse option arguments that start with "-" -while getopts :-: optchar; do - case "${optchar}" in - -) - case "${OPTARG}" in - help ) cli_help;; - clean ) find_python && clean;; - dev ) CFG_REQUIREMENTS="$DEV_REQUIREMENTS";; - docs ) CFG_REQUIREMENTS="$DOCS_REQUIREMENTS";; - esac;; - esac -done - - -PIP_EXTRA_ARGS="$PIP_EXTRA_ARGS" - -find_python -create_virtualenv "$VIRTUALENV_DIR" -install_packages "$CFG_REQUIREMENTS" -. "$CFG_BIN_DIR/activate" - - -set +e diff --git a/packagedb/configure.bat b/packagedb/configure.bat deleted file mode 100644 index 41547cc5..00000000 --- a/packagedb/configure.bat +++ /dev/null @@ -1,207 +0,0 @@ -@echo OFF -@setlocal - -@rem Copyright (c) nexB Inc. and others. All rights reserved. -@rem SPDX-License-Identifier: Apache-2.0 -@rem See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -@rem See https://github.com/nexB/ for support or download. -@rem See https://aboutcode.org for more information about nexB OSS projects. - - -@rem ################################ -@rem # A configuration script to set things up: -@rem # create a virtualenv and install or update thirdparty packages. -@rem # Source this script for initial configuration -@rem # Use configure --help for details - -@rem # NOTE: please keep in sync with POSIX script configure - -@rem # This script will search for a virtualenv.pyz app in etc\thirdparty\virtualenv.pyz -@rem # Otherwise it will download the latest from the VIRTUALENV_PYZ_URL default -@rem ################################ - - -@rem ################################ -@rem # Defaults. Change these variables to customize this script -@rem ################################ - -@rem # Requirement arguments passed to pip and used by default or with --dev. -set "REQUIREMENTS=--editable . --constraint requirements.txt" -set "DEV_REQUIREMENTS=--editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt" -set "DOCS_REQUIREMENTS=--editable .[docs] --constraint requirements.txt" - -@rem # where we create a virtualenv -set "VIRTUALENV_DIR=venv" - -@rem # Cleanable files and directories to delete with the --clean option -set "CLEANABLE=build venv" - -@rem # extra arguments passed to pip -set "PIP_EXTRA_ARGS= " - -@rem # the URL to download virtualenv.pyz if needed -set VIRTUALENV_PYZ_URL=https://bootstrap.pypa.io/virtualenv.pyz -@rem ################################ - - -@rem ################################ -@rem # Current directory where this script lives -set CFG_ROOT_DIR=%~dp0 -set "CFG_BIN_DIR=%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\Scripts" - - -@rem ################################ -@rem # Thirdparty package locations and index handling -@rem # Find packages from the local thirdparty directory -if exist "%CFG_ROOT_DIR%\thirdparty" ( - set PIP_EXTRA_ARGS=--find-links "%CFG_ROOT_DIR%\thirdparty" -) - - -@rem ################################ -@rem # Set the quiet flag to empty if not defined -if not defined CFG_QUIET ( - set "CFG_QUIET= " -) - - -@rem ################################ -@rem # Main command line entry point -set "CFG_REQUIREMENTS=%REQUIREMENTS%" - -:again -if not "%1" == "" ( - if "%1" EQU "--help" (goto cli_help) - if "%1" EQU "--clean" (goto clean) - if "%1" EQU "--dev" ( - set "CFG_REQUIREMENTS=%DEV_REQUIREMENTS%" - ) - if "%1" EQU "--docs" ( - set "CFG_REQUIREMENTS=%DOCS_REQUIREMENTS%" - ) - shift - goto again -) - -set "PIP_EXTRA_ARGS=%PIP_EXTRA_ARGS%" - - -@rem ################################ -@rem # Find a proper Python to run -@rem # Use environment variables or a file if available. -@rem # Otherwise the latest Python by default. -if not defined PYTHON_EXECUTABLE ( - @rem # check for a file named PYTHON_EXECUTABLE - if exist "%CFG_ROOT_DIR%\PYTHON_EXECUTABLE" ( - set /p PYTHON_EXECUTABLE=<"%CFG_ROOT_DIR%\PYTHON_EXECUTABLE" - ) else ( - set "PYTHON_EXECUTABLE=py" - ) -) - - -@rem ################################ -:create_virtualenv -@rem # create a virtualenv for Python -@rem # Note: we do not use the bundled Python 3 "venv" because its behavior and -@rem # presence is not consistent across Linux distro and sometimes pip is not -@rem # included either by default. The virtualenv.pyz app cures all these issues. - -if not exist "%CFG_BIN_DIR%\python.exe" ( - if not exist "%CFG_BIN_DIR%" ( - mkdir "%CFG_BIN_DIR%" - ) - - if exist "%CFG_ROOT_DIR%\etc\thirdparty\virtualenv.pyz" ( - %PYTHON_EXECUTABLE% "%CFG_ROOT_DIR%\etc\thirdparty\virtualenv.pyz" ^ - --wheel embed --pip embed --setuptools embed ^ - --seeder pip ^ - --never-download ^ - --no-periodic-update ^ - --no-vcs-ignore ^ - %CFG_QUIET% ^ - "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%" - ) else ( - if not exist "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\virtualenv.pyz" ( - curl -o "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\virtualenv.pyz" %VIRTUALENV_PYZ_URL% - - if %ERRORLEVEL% neq 0 ( - exit /b %ERRORLEVEL% - ) - ) - %PYTHON_EXECUTABLE% "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\virtualenv.pyz" ^ - --wheel embed --pip embed --setuptools embed ^ - --seeder pip ^ - --never-download ^ - --no-periodic-update ^ - --no-vcs-ignore ^ - %CFG_QUIET% ^ - "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%" - ) -) - -if %ERRORLEVEL% neq 0 ( - exit /b %ERRORLEVEL% -) - - -@rem ################################ -:install_packages -@rem # install requirements in virtualenv -@rem # note: --no-build-isolation means that pip/wheel/setuptools will not -@rem # be reinstalled a second time and reused from the virtualenv and this -@rem # speeds up the installation. -@rem # We always have the PEP517 build dependencies installed already. - -"%CFG_BIN_DIR%\pip" install ^ - --upgrade ^ - --no-build-isolation ^ - %CFG_QUIET% ^ - %PIP_EXTRA_ARGS% ^ - %CFG_REQUIREMENTS% - - -@rem ################################ -:create_bin_junction -@rem # Create junction to bin to have the same directory between linux and windows -if exist "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\bin" ( - rmdir /s /q "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\bin" -) -mklink /J "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\bin" "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\Scripts" - -if %ERRORLEVEL% neq 0 ( - exit /b %ERRORLEVEL% -) - -exit /b 0 - - -@rem ################################ -:cli_help - echo An initial configuration script - echo " usage: configure [options]" - echo " " - echo The default is to configure for regular use. Use --dev for development. - echo " " - echo The options are: - echo " --clean: clean built and installed files and exit." - echo " --dev: configure the environment for development." - echo " --help: display this help message and exit." - echo " " - echo By default, the python interpreter version found in the path is used. - echo Alternatively, the PYTHON_EXECUTABLE environment variable can be set to - echo configure another Python executable interpreter to use. If this is not - echo set, a file named PYTHON_EXECUTABLE containing a single line with the - echo path of the Python executable to use will be checked last. - exit /b 0 - - -@rem ################################ -:clean -@rem # Remove cleanable file and directories and files from the root dir. -echo "* Cleaning ..." -for %%F in (%CLEANABLE%) do ( - rmdir /s /q "%CFG_ROOT_DIR%\%%F" >nul 2>&1 - del /f /q "%CFG_ROOT_DIR%\%%F" >nul 2>&1 -) -exit /b 0 diff --git a/packagedb/manage.py b/packagedb/manage.py deleted file mode 100755 index 5b68baf3..00000000 --- a/packagedb/manage.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "packagedbio.settings") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff --git a/packagedb/src/packagedb/migrations/0001_initial.py b/packagedb/migrations/0001_initial.py similarity index 100% rename from packagedb/src/packagedb/migrations/0001_initial.py rename to packagedb/migrations/0001_initial.py diff --git a/packagedb/src/packagedb/migrations/0002_auto_20160707_1018.py b/packagedb/migrations/0002_auto_20160707_1018.py similarity index 100% rename from packagedb/src/packagedb/migrations/0002_auto_20160707_1018.py rename to packagedb/migrations/0002_auto_20160707_1018.py diff --git a/packagedb/src/packagedb/migrations/0003_auto_20160708_1513.py b/packagedb/migrations/0003_auto_20160708_1513.py similarity index 100% rename from packagedb/src/packagedb/migrations/0003_auto_20160708_1513.py rename to packagedb/migrations/0003_auto_20160708_1513.py diff --git a/packagedb/src/packagedb/migrations/0004_auto_20160713_0022.py b/packagedb/migrations/0004_auto_20160713_0022.py similarity index 100% rename from packagedb/src/packagedb/migrations/0004_auto_20160713_0022.py rename to packagedb/migrations/0004_auto_20160713_0022.py diff --git a/packagedb/src/packagedb/migrations/0005_auto_20170217_0309.py b/packagedb/migrations/0005_auto_20170217_0309.py similarity index 100% rename from packagedb/src/packagedb/migrations/0005_auto_20170217_0309.py rename to packagedb/migrations/0005_auto_20170217_0309.py diff --git a/packagedb/src/packagedb/migrations/0006_package_mining_level.py b/packagedb/migrations/0006_package_mining_level.py similarity index 100% rename from packagedb/src/packagedb/migrations/0006_package_mining_level.py rename to packagedb/migrations/0006_package_mining_level.py diff --git a/packagedb/src/packagedb/migrations/0007_auto_20180713_0144.py b/packagedb/migrations/0007_auto_20180713_0144.py similarity index 100% rename from packagedb/src/packagedb/migrations/0007_auto_20180713_0144.py rename to packagedb/migrations/0007_auto_20180713_0144.py diff --git a/packagedb/src/packagedb/migrations/0008_package_package_url.py b/packagedb/migrations/0008_package_package_url.py similarity index 100% rename from packagedb/src/packagedb/migrations/0008_package_package_url.py rename to packagedb/migrations/0008_package_package_url.py diff --git a/packagedb/src/packagedb/migrations/0009_auto_20180918_1225.py b/packagedb/migrations/0009_auto_20180918_1225.py similarity index 100% rename from packagedb/src/packagedb/migrations/0009_auto_20180918_1225.py rename to packagedb/migrations/0009_auto_20180918_1225.py diff --git a/packagedb/src/packagedb/migrations/0010_auto_20180919_1740.py b/packagedb/migrations/0010_auto_20180919_1740.py similarity index 100% rename from packagedb/src/packagedb/migrations/0010_auto_20180919_1740.py rename to packagedb/migrations/0010_auto_20180919_1740.py diff --git a/packagedb/src/packagedb/migrations/0011_auto_20180921_1129.py b/packagedb/migrations/0011_auto_20180921_1129.py similarity index 100% rename from packagedb/src/packagedb/migrations/0011_auto_20180921_1129.py rename to packagedb/migrations/0011_auto_20180921_1129.py diff --git a/packagedb/src/packagedb/migrations/0012_auto_20181001_1120.py b/packagedb/migrations/0012_auto_20181001_1120.py similarity index 100% rename from packagedb/src/packagedb/migrations/0012_auto_20181001_1120.py rename to packagedb/migrations/0012_auto_20181001_1120.py diff --git a/packagedb/src/packagedb/migrations/0013_auto_20181001_1209.py b/packagedb/migrations/0013_auto_20181001_1209.py similarity index 100% rename from packagedb/src/packagedb/migrations/0013_auto_20181001_1209.py rename to packagedb/migrations/0013_auto_20181001_1209.py diff --git a/packagedb/src/packagedb/migrations/0014_remove_package_package_url.py b/packagedb/migrations/0014_remove_package_package_url.py similarity index 100% rename from packagedb/src/packagedb/migrations/0014_remove_package_package_url.py rename to packagedb/migrations/0014_remove_package_package_url.py diff --git a/packagedb/src/packagedb/migrations/0015_remove_package_download_checksums.py b/packagedb/migrations/0015_remove_package_download_checksums.py similarity index 100% rename from packagedb/src/packagedb/migrations/0015_remove_package_download_checksums.py rename to packagedb/migrations/0015_remove_package_download_checksums.py diff --git a/packagedb/src/packagedb/migrations/0016_auto_20181023_1211.py b/packagedb/migrations/0016_auto_20181023_1211.py similarity index 100% rename from packagedb/src/packagedb/migrations/0016_auto_20181023_1211.py rename to packagedb/migrations/0016_auto_20181023_1211.py diff --git a/packagedb/src/packagedb/migrations/0017_auto_20181023_1211.py b/packagedb/migrations/0017_auto_20181023_1211.py similarity index 100% rename from packagedb/src/packagedb/migrations/0017_auto_20181023_1211.py rename to packagedb/migrations/0017_auto_20181023_1211.py diff --git a/packagedb/src/packagedb/migrations/0018_auto_20181023_1212.py b/packagedb/migrations/0018_auto_20181023_1212.py similarity index 100% rename from packagedb/src/packagedb/migrations/0018_auto_20181023_1212.py rename to packagedb/migrations/0018_auto_20181023_1212.py diff --git a/packagedb/src/packagedb/migrations/0019_auto_20181023_1212.py b/packagedb/migrations/0019_auto_20181023_1212.py similarity index 100% rename from packagedb/src/packagedb/migrations/0019_auto_20181023_1212.py rename to packagedb/migrations/0019_auto_20181023_1212.py diff --git a/packagedb/src/packagedb/migrations/0020_package_download_sha256.py b/packagedb/migrations/0020_package_download_sha256.py similarity index 100% rename from packagedb/src/packagedb/migrations/0020_package_download_sha256.py rename to packagedb/migrations/0020_package_download_sha256.py diff --git a/packagedb/src/packagedb/migrations/0021_package_download_sha512.py b/packagedb/migrations/0021_package_download_sha512.py similarity index 100% rename from packagedb/src/packagedb/migrations/0021_package_download_sha512.py rename to packagedb/migrations/0021_package_download_sha512.py diff --git a/packagedb/src/packagedb/migrations/0022_package_manifest_path.py b/packagedb/migrations/0022_package_manifest_path.py similarity index 100% rename from packagedb/src/packagedb/migrations/0022_package_manifest_path.py rename to packagedb/migrations/0022_package_manifest_path.py diff --git a/packagedb/src/packagedb/migrations/0023_package_source_packages.py b/packagedb/migrations/0023_package_source_packages.py similarity index 100% rename from packagedb/src/packagedb/migrations/0023_package_source_packages.py rename to packagedb/migrations/0023_package_source_packages.py diff --git a/packagedb/src/packagedb/migrations/0024_auto_20181030_1817.py b/packagedb/migrations/0024_auto_20181030_1817.py similarity index 100% rename from packagedb/src/packagedb/migrations/0024_auto_20181030_1817.py rename to packagedb/migrations/0024_auto_20181030_1817.py diff --git a/packagedb/src/packagedb/migrations/0025_auto_20181030_1817.py b/packagedb/migrations/0025_auto_20181030_1817.py similarity index 100% rename from packagedb/src/packagedb/migrations/0025_auto_20181030_1817.py rename to packagedb/migrations/0025_auto_20181030_1817.py diff --git a/packagedb/src/packagedb/migrations/0026_auto_20181030_1824.py b/packagedb/migrations/0026_auto_20181030_1824.py similarity index 100% rename from packagedb/src/packagedb/migrations/0026_auto_20181030_1824.py rename to packagedb/migrations/0026_auto_20181030_1824.py diff --git a/packagedb/src/packagedb/migrations/0027_auto_20181030_1825.py b/packagedb/migrations/0027_auto_20181030_1825.py similarity index 100% rename from packagedb/src/packagedb/migrations/0027_auto_20181030_1825.py rename to packagedb/migrations/0027_auto_20181030_1825.py diff --git a/packagedb/src/packagedb/migrations/0028_auto_20181127_0224.py b/packagedb/migrations/0028_auto_20181127_0224.py similarity index 100% rename from packagedb/src/packagedb/migrations/0028_auto_20181127_0224.py rename to packagedb/migrations/0028_auto_20181127_0224.py diff --git a/packagedb/src/packagedb/migrations/0029_auto_20181127_0246.py b/packagedb/migrations/0029_auto_20181127_0246.py similarity index 100% rename from packagedb/src/packagedb/migrations/0029_auto_20181127_0246.py rename to packagedb/migrations/0029_auto_20181127_0246.py diff --git a/packagedb/src/packagedb/migrations/0030_auto_20190107_1616.py b/packagedb/migrations/0030_auto_20190107_1616.py similarity index 100% rename from packagedb/src/packagedb/migrations/0030_auto_20190107_1616.py rename to packagedb/migrations/0030_auto_20190107_1616.py diff --git a/packagedb/src/packagedb/migrations/0031_auto_20190110_2354.py b/packagedb/migrations/0031_auto_20190110_2354.py similarity index 100% rename from packagedb/src/packagedb/migrations/0031_auto_20190110_2354.py rename to packagedb/migrations/0031_auto_20190110_2354.py diff --git a/packagedb/src/packagedb/migrations/0032_auto_20190125_0019.py b/packagedb/migrations/0032_auto_20190125_0019.py similarity index 100% rename from packagedb/src/packagedb/migrations/0032_auto_20190125_0019.py rename to packagedb/migrations/0032_auto_20190125_0019.py diff --git a/packagedb/src/packagedb/migrations/0033_auto_20190128_2056.py b/packagedb/migrations/0033_auto_20190128_2056.py similarity index 100% rename from packagedb/src/packagedb/migrations/0033_auto_20190128_2056.py rename to packagedb/migrations/0033_auto_20190128_2056.py diff --git a/packagedb/src/packagedb/migrations/0034_auto_20200407_2232.py b/packagedb/migrations/0034_auto_20200407_2232.py similarity index 100% rename from packagedb/src/packagedb/migrations/0034_auto_20200407_2232.py rename to packagedb/migrations/0034_auto_20200407_2232.py diff --git a/packagedb/src/packagedb/migrations/0035_auto_20200408_2126.py b/packagedb/migrations/0035_auto_20200408_2126.py similarity index 100% rename from packagedb/src/packagedb/migrations/0035_auto_20200408_2126.py rename to packagedb/migrations/0035_auto_20200408_2126.py diff --git a/packagedb/src/packagedb/migrations/0036_auto_20200416_2131.py b/packagedb/migrations/0036_auto_20200416_2131.py similarity index 100% rename from packagedb/src/packagedb/migrations/0036_auto_20200416_2131.py rename to packagedb/migrations/0036_auto_20200416_2131.py diff --git a/packagedb/src/packagedb/migrations/0037_auto_20200423_1242.py b/packagedb/migrations/0037_auto_20200423_1242.py similarity index 100% rename from packagedb/src/packagedb/migrations/0037_auto_20200423_1242.py rename to packagedb/migrations/0037_auto_20200423_1242.py diff --git a/packagedb/src/packagedb/migrations/0038_add_index_for_filter_fields.py b/packagedb/migrations/0038_add_index_for_filter_fields.py similarity index 100% rename from packagedb/src/packagedb/migrations/0038_add_index_for_filter_fields.py rename to packagedb/migrations/0038_add_index_for_filter_fields.py diff --git a/packagedb/src/packagedb/migrations/0039_packageurl_python_field_updates.py b/packagedb/migrations/0039_packageurl_python_field_updates.py similarity index 100% rename from packagedb/src/packagedb/migrations/0039_packageurl_python_field_updates.py rename to packagedb/migrations/0039_packageurl_python_field_updates.py diff --git a/packagedb/src/packagedb/migrations/0040_add_root_path.py b/packagedb/migrations/0040_add_root_path.py similarity index 100% rename from packagedb/src/packagedb/migrations/0040_add_root_path.py rename to packagedb/migrations/0040_add_root_path.py diff --git a/packagedb/src/packagedb/migrations/0041_update_ordering_to_id.py b/packagedb/migrations/0041_update_ordering_to_id.py similarity index 100% rename from packagedb/src/packagedb/migrations/0041_update_ordering_to_id.py rename to packagedb/migrations/0041_update_ordering_to_id.py diff --git a/packagedb/src/packagedb/migrations/0042_update_fields_to_django3_standards.py b/packagedb/migrations/0042_update_fields_to_django3_standards.py similarity index 100% rename from packagedb/src/packagedb/migrations/0042_update_fields_to_django3_standards.py rename to packagedb/migrations/0042_update_fields_to_django3_standards.py diff --git a/packagedb/src/packagedb/migrations/0043_lowercase_purl_fields.py b/packagedb/migrations/0043_lowercase_purl_fields.py similarity index 100% rename from packagedb/src/packagedb/migrations/0043_lowercase_purl_fields.py rename to packagedb/migrations/0043_lowercase_purl_fields.py diff --git a/packagedb/src/packagedb/migrations/0044_add_history_field.py b/packagedb/migrations/0044_add_history_field.py similarity index 100% rename from packagedb/src/packagedb/migrations/0044_add_history_field.py rename to packagedb/migrations/0044_add_history_field.py diff --git a/packagedb/src/packagedb/migrations/0045_relax_license_expression_max_length_for_resources.py b/packagedb/migrations/0045_relax_license_expression_max_length_for_resources.py similarity index 100% rename from packagedb/src/packagedb/migrations/0045_relax_license_expression_max_length_for_resources.py rename to packagedb/migrations/0045_relax_license_expression_max_length_for_resources.py diff --git a/packagedb/src/packagedb/migrations/0046_add_extra_data_to_package.py b/packagedb/migrations/0046_add_extra_data_to_package.py similarity index 100% rename from packagedb/src/packagedb/migrations/0046_add_extra_data_to_package.py rename to packagedb/migrations/0046_add_extra_data_to_package.py diff --git a/packagedb/src/packagedb/migrations/0047_add_search_vector_field_to_package.py b/packagedb/migrations/0047_add_search_vector_field_to_package.py similarity index 100% rename from packagedb/src/packagedb/migrations/0047_add_search_vector_field_to_package.py rename to packagedb/migrations/0047_add_search_vector_field_to_package.py diff --git a/packagedb/src/packagedb/migrations/0048_add_gin_index_to_search_vector_field.py b/packagedb/migrations/0048_add_gin_index_to_search_vector_field.py similarity index 100% rename from packagedb/src/packagedb/migrations/0048_add_gin_index_to_search_vector_field.py rename to packagedb/migrations/0048_add_gin_index_to_search_vector_field.py diff --git a/packagedb/src/packagedb/migrations/0049_alter_resource_extra_data.py b/packagedb/migrations/0049_alter_resource_extra_data.py similarity index 100% rename from packagedb/src/packagedb/migrations/0049_alter_resource_extra_data.py rename to packagedb/migrations/0049_alter_resource_extra_data.py diff --git a/packagedb/src/packagedb/migrations/0050_alter_resource_extra_data.py b/packagedb/migrations/0050_alter_resource_extra_data.py similarity index 100% rename from packagedb/src/packagedb/migrations/0050_alter_resource_extra_data.py rename to packagedb/migrations/0050_alter_resource_extra_data.py diff --git a/packagedb/src/packagedb/migrations/0051_package_api_data_url_package_datasource_id_and_more.py b/packagedb/migrations/0051_package_api_data_url_package_datasource_id_and_more.py similarity index 100% rename from packagedb/src/packagedb/migrations/0051_package_api_data_url_package_datasource_id_and_more.py rename to packagedb/migrations/0051_package_api_data_url_package_datasource_id_and_more.py diff --git a/packagedb/src/packagedb/migrations/0052_package_index_error_package_last_indexed_date.py b/packagedb/migrations/0052_package_index_error_package_last_indexed_date.py similarity index 100% rename from packagedb/src/packagedb/migrations/0052_package_index_error_package_last_indexed_date.py rename to packagedb/migrations/0052_package_index_error_package_last_indexed_date.py diff --git a/minecode/src/discovery/migrations/__init__.py b/packagedb/migrations/__init__.py similarity index 100% rename from minecode/src/discovery/migrations/__init__.py rename to packagedb/migrations/__init__.py diff --git a/packagedb/src/packagedb/models.py b/packagedb/models.py similarity index 100% rename from packagedb/src/packagedb/models.py rename to packagedb/models.py diff --git a/packagedb/pyproject.toml b/packagedb/pyproject.toml deleted file mode 100644 index b7906615..00000000 --- a/packagedb/pyproject.toml +++ /dev/null @@ -1,54 +0,0 @@ -[build-system] -requires = ["setuptools >= 50", "wheel"] -# requires = ["setuptools >= 50", "wheel", "setuptools_scm[toml] >= 6"] - -build-backend = "setuptools.build_meta" - -[tool.setuptools_scm] -# this is used populated when creating a git archive -# and when there is .git dir and/or there is no git installed -fallback_version = "9999.$Format:%h-%cs$" - -[tool.pytest.ini_options] -norecursedirs = [ - ".git", - "bin", - "dist", - "build", - "_build", - "dist", - "etc", - "local", - "ci", - "docs", - "man", - "share", - "samples", - ".cache", - ".settings", - "Include", - "include", - "Lib", - "lib", - "lib64", - "Lib64", - "Scripts", - "thirdparty", - "tmp", - "venv", - "tests/data", - ".eggs", - "src/*/data", - "tests/*/data" -] - -python_files = "*.py" - -python_classes = "Test" -python_functions = "test" - -addopts = [ - "-rfExXw", - "--strict-markers", - "--doctest-modules" -] diff --git a/packagedb/requirements-dev.txt b/packagedb/requirements-dev.txt deleted file mode 100644 index ec140d37..00000000 --- a/packagedb/requirements-dev.txt +++ /dev/null @@ -1,26 +0,0 @@ -aboutcode-toolkit==7.0.2 -attrs==22.1.0 -black==22.10.0 -boolean.py==4.0 -certifi==2022.9.24 -click==8.1.3 -et-xmlfile==1.1.0 -execnet==1.9.0 -iniconfig==1.1.1 -jinja2==3.1.2 -license-expression==30.0.0 -markupsafe==2.1.1 -mypy-extensions==0.4.3 -openpyxl==3.0.10 -packaging==21.3 -pathspec==0.10.1 -platformdirs==2.5.2 -pluggy==1.0.0 -py==1.11.0 -pyparsing==3.0.9 -pytest==7.1.3 -pytest-forked==1.4.0 -pytest-xdist==2.5.0 -pyyaml==6.0 -saneyaml==0.5.2 -tomli==2.0.1 \ No newline at end of file diff --git a/packagedb/requirements.txt b/packagedb/requirements.txt deleted file mode 100644 index 20ae3a76..00000000 --- a/packagedb/requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -asgiref==3.5.2 -Django==4.1.2 -django-filter==22.1 -djangorestframework==3.14.0 -django-environ==0.8.1 -natsort==8.2.0 -packageurl-python==0.10.4 -psycopg2-binary==2.9.3 -pytz==2022.5 -setuptools==65.3.0 -sqlparse==0.4.3 diff --git a/packagedb/src/packagedb/serializers.py b/packagedb/serializers.py similarity index 100% rename from packagedb/src/packagedb/serializers.py rename to packagedb/serializers.py diff --git a/packagedb/setup.cfg b/packagedb/setup.cfg deleted file mode 100644 index 26049997..00000000 --- a/packagedb/setup.cfg +++ /dev/null @@ -1,70 +0,0 @@ -[metadata] -name = packagedb -version = 2.0.0 - -author = nexB. Inc. and others -author_email = info@nexb.com -license = license = Apache-2.0 AND CC-BY-SA-4.0 - -# description must be on ONE line https://github.com/pypa/setuptools/issues/1390 -description = A purl (Package URL) Database -long_description = file:README.rst -url = https://github.com/nexB/purldb/packagedb -classifiers = - Programming Language :: Python - Programming Language :: Python :: 3 - Topic :: Utilities - -keywords = - packagedb - scancode - purl - purldb - -license_files = - apache-2.0.LICENSE - cc-by-sa-4.0.LICENSE - CHANGELOG.rst - README.rst - AUTHORS.rst - NOTICE - - -[options] -#setup_requires = setuptools_scm[toml] >= 4 - -package_dir = - =src -packages = find: -include_package_data = true -zip_safe = false -install_requires = - Django>=4.1.2 - djangorestframework>=3.14.0 - django-environ>=0.8.0 - django-filter>=22.1 - psycopg2-binary>=2.9.3 - packageurl-python>=0.10.4 - natsort>=8.2.0 - -python_requires = >=3.8.* - - -[options.packages.find] -where = src - - -[options.extras_require] -testing = - pytest >= 6, != 7.0.0 - pytest-xdist >= 2 - pytest-django - aboutcode-toolkit >= 6.0.0 - black - mock - - -docs = - Sphinx >= 3.3.1 - sphinx-rtd-theme >= 0.5.0 - doc8 >= 0.8.1 diff --git a/packagedb/setup.py b/packagedb/setup.py deleted file mode 100644 index bac24a43..00000000 --- a/packagedb/setup.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python - -import setuptools - -if __name__ == "__main__": - setuptools.setup() diff --git a/packagedb/src/packagedb/signals.py b/packagedb/signals.py similarity index 100% rename from packagedb/src/packagedb/signals.py rename to packagedb/signals.py diff --git a/packagedb/src/packagedb/__init__.py b/packagedb/src/packagedb/__init__.py deleted file mode 100644 index 2eb8f9f0..00000000 --- a/packagedb/src/packagedb/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# diff --git a/packagedb/src/packagedb/api_custom.py b/packagedb/src/packagedb/api_custom.py deleted file mode 100644 index 5686311f..00000000 --- a/packagedb/src/packagedb/api_custom.py +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -from rest_framework.pagination import PageNumberPagination - - -class PageSizePagination(PageNumberPagination): - """ - Adds the page_size parameter. Default results per page is 10. - A page_size parameter can be provided, limited to 100 results per page max. - For example: - http://api.example.org/accounts/?page=4&page_size=100 - """ - page_size = 10 - max_page_size = 100 - page_size_query_param = 'page_size' diff --git a/packagedb/src/packagedbio/__init__.py b/packagedb/src/packagedbio/__init__.py deleted file mode 100644 index 2eb8f9f0..00000000 --- a/packagedb/src/packagedbio/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# diff --git a/packagedb/src/packagedbio/settings.py b/packagedb/src/packagedbio/settings.py deleted file mode 100644 index da14f91e..00000000 --- a/packagedb/src/packagedbio/settings.py +++ /dev/null @@ -1,263 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -import sys -from pathlib import Path - -import environ - -PROJECT_DIR = Path(__file__).resolve().parent -ROOT_DIR = PROJECT_DIR.parent.parent - -# Environment - -ENV_FILE = "/etc/packagedb/.env" -if not Path(ENV_FILE).exists(): - ENV_FILE = ROOT_DIR / ".env" - -env = environ.Env() -environ.Env.read_env(str(ENV_FILE)) - -# Security - -SECRET_KEY = env.str("SECRET_KEY") - -ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=[".localhost", "127.0.0.1", "[::1]"]) - -# SECURITY WARNING: do not run with debug turned on in production -DEBUG = env.bool("PACKAGEDB_DEBUG", default=False) - -PACKAGEDB_REQUIRE_AUTHENTICATION = env.bool( - "PACKAGEDB_REQUIRE_AUTHENTICATION", default=False -) - -# SECURITY WARNING: do not run with debug turned on in production -DEBUG_TOOLBAR = env.bool("PACKAGEDB_DEBUG_TOOLBAR", default=False) - -PACKAGEDB_PASSWORD_MIN_LENGTH = env.int("PACKAGEDB_PASSWORD_MIN_LENGTH", default=14) - -# PackageDB - -PACKAGEDB_LOG_LEVEL = env.str("PACKAGEDB_LOG_LEVEL", "INFO") - -# Application definition - -INSTALLED_APPS = ( - # Local apps - # Must come before Third-party apps for proper templates override - 'packagedb', - # Django built-in - "django.contrib.auth", - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.admin', - "django.contrib.humanize", - # Third-party apps - 'django_filters', - 'rest_framework', -) - -MIDDLEWARE = ( - "django.middleware.security.SecurityMiddleware", - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) - -ROOT_URLCONF = 'packagedbio.urls' - -WSGI_APPLICATION = "packagedbio.wsgi.application" - -# Database - -DATABASES = { - "default": { - "ENGINE": env.str("PACKAGEDB_DB_ENGINE", "django.db.backends.postgresql"), - "HOST": env.str("PACKAGEDB_DB_HOST", "localhost"), - "NAME": env.str("PACKAGEDB_DB_NAME", "packagedb"), - "USER": env.str("PACKAGEDB_DB_USER", "packagedb"), - "PASSWORD": env.str("PACKAGEDB_DB_PASSWORD", "packagedb"), - "PORT": env.str("PACKAGEDB_DB_PORT", "5432"), - "ATOMIC_REQUESTS": True, - } -} - -DEFAULT_AUTO_FIELD = "django.db.models.AutoField" - -# Templates - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - "DIRS": [str(PROJECT_DIR.joinpath("templates"))], - "APP_DIRS": True, - 'OPTIONS': { - "debug": DEBUG, - 'context_processors': [ - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - 'django.template.context_processors.request', - "django.template.context_processors.static", - ], - }, - }, -] - -# Login - -LOGIN_REDIRECT_URL = "/" -LOGOUT_REDIRECT_URL = "/" - -# Passwords - -AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", - "OPTIONS": { - "min_length": PACKAGEDB_PASSWORD_MIN_LENGTH, - }, - }, - { - "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", - }, -] - -# Testing - -# True if running tests through `./manage test or pytest` -IS_TESTS = any(clue in sys.argv for clue in ("test", "pytest")) - -# Cache - -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - "LOCATION": "default", - } -} - -# Logging - -LOGGING = { - "version": 1, - "disable_existing_loggers": False, - "formatters": { - "simple": { - "format": "{levelname} {message}", - "style": "{", - }, - }, - "handlers": { - "null": { - "class": "logging.NullHandler", - }, - "console": { - "class": "logging.StreamHandler", - "formatter": "simple", - }, - }, - "loggers": { - "scanpipe": { - "handlers": ["null"] if IS_TESTS else ["console"], - "level": PACKAGEDB_LOG_LEVEL, - "propagate": False, - }, - "django": { - "handlers": ["null"] if IS_TESTS else ["console"], - "propagate": False, - }, - # Set PACKAGEDB_LOG_LEVEL=DEBUG to display all SQL queries in the console. - "django.db.backends": { - "level": PACKAGEDB_LOG_LEVEL, - }, - }, -} - -# Internationalization - -LANGUAGE_CODE = "en-us" - -TIME_ZONE = env.str("TIME_ZONE", default="UTC") - -USE_I18N = True - -USE_TZ = True - -# Static files (CSS, JavaScript, Images) - -STATIC_URL = '/static/' - -STATIC_ROOT = env.str("PACKAGEDB_STATIC_ROOT", "./") - -STATICFILES_DIRS = [ - str(PROJECT_DIR / "packagedbio" / "static"), -] - -# Third-party apps - -# Django restframework - -REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION_CLASSES': (), - "DEFAULT_PERMISSION_CLASSES": (), - 'DEFAULT_RENDERER_CLASSES': ( - 'rest_framework.renderers.JSONRenderer', - 'rest_framework.renderers.BrowsableAPIRenderer', - 'rest_framework.renderers.AdminRenderer', - ), - 'DEFAULT_FILTER_BACKENDS': ( - 'django_filters.rest_framework.DjangoFilterBackend', - 'rest_framework.filters.SearchFilter', - ), - 'DEFAULT_PAGINATION_CLASS': 'packagedb.api_custom.PageSizePagination', - # Limit the load on the Database returning a small number of records by default. https://github.com/nexB/vulnerablecode/issues/819 - "PAGE_SIZE": 10, -} - -if not PACKAGEDB_REQUIRE_AUTHENTICATION: - REST_FRAMEWORK["DEFAULT_PERMISSION_CLASSES"] = ( - "rest_framework.permissions.AllowAny", - ) - -if DEBUG_TOOLBAR: - INSTALLED_APPS += ("debug_toolbar",) - - MIDDLEWARE += ("debug_toolbar.middleware.DebugToolbarMiddleware",) - - DEBUG_TOOLBAR_PANELS = ( - "debug_toolbar.panels.history.HistoryPanel", - "debug_toolbar.panels.versions.VersionsPanel", - "debug_toolbar.panels.timer.TimerPanel", - "debug_toolbar.panels.settings.SettingsPanel", - "debug_toolbar.panels.headers.HeadersPanel", - "debug_toolbar.panels.request.RequestPanel", - "debug_toolbar.panels.sql.SQLPanel", - "debug_toolbar.panels.staticfiles.StaticFilesPanel", - "debug_toolbar.panels.templates.TemplatesPanel", - "debug_toolbar.panels.cache.CachePanel", - "debug_toolbar.panels.signals.SignalsPanel", - "debug_toolbar.panels.logging.LoggingPanel", - "debug_toolbar.panels.redirects.RedirectsPanel", - "debug_toolbar.panels.profiling.ProfilingPanel", - ) - - INTERNAL_IPS = [ - "127.0.0.1", - ] diff --git a/packagedb/src/packagedbio/static/.keep b/packagedb/src/packagedbio/static/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/packagedb/src/packagedbio/urls.py b/packagedb/src/packagedbio/urls.py deleted file mode 100644 index 9c7ae058..00000000 --- a/packagedb/src/packagedbio/urls.py +++ /dev/null @@ -1,25 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -from django.conf.urls import include -from django.urls import re_path - -from rest_framework import routers - -from packagedb.api import PackageViewSet -from packagedb.api import ResourceViewSet - - -api_router = routers.DefaultRouter() -api_router.register(r'packages', PackageViewSet) -api_router.register(r'resources', ResourceViewSet) - -urlpatterns = [ - re_path(r'^api/', include((api_router.urls, 'api'))), -] diff --git a/packagedb/src/packagedbio/wsgi.py b/packagedb/src/packagedbio/wsgi.py deleted file mode 100644 index c790c78e..00000000 --- a/packagedb/src/packagedbio/wsgi.py +++ /dev/null @@ -1,23 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -import os -from django.core.wsgi import get_wsgi_application - - -""" -WSGI config for packagedbio project. - -It exposes the WSGI callable as a module-level variable named ``application``. -""" - - -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'packagedbio.settings') - -application = get_wsgi_application() diff --git a/packagedb/tests/packagedb/__init__.py b/packagedb/tests/packagedb/__init__.py deleted file mode 100644 index 2eb8f9f0..00000000 --- a/packagedb/tests/packagedb/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# diff --git a/packagedb/tests/packagedb/tests/__init__.py b/packagedb/tests/packagedb/tests/__init__.py deleted file mode 100644 index 2eb8f9f0..00000000 --- a/packagedb/tests/packagedb/tests/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# diff --git a/packagedb/tests/packagedb/tests/test_api.py b/packagedb/tests/test_api.py similarity index 100% rename from packagedb/tests/packagedb/tests/test_api.py rename to packagedb/tests/test_api.py diff --git a/packagedb/tests/packagedb/tests/test_models.py b/packagedb/tests/test_models.py similarity index 100% rename from packagedb/tests/packagedb/tests/test_models.py rename to packagedb/tests/test_models.py diff --git a/packagedb/tests/test_skeleton_codestyle.py b/packagedb/tests/test_skeleton_codestyle.py deleted file mode 100644 index 2eb6e558..00000000 --- a/packagedb/tests/test_skeleton_codestyle.py +++ /dev/null @@ -1,36 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# ScanCode is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/skeleton for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -import subprocess -import unittest -import configparser - - -class BaseTests(unittest.TestCase): - def test_skeleton_codestyle(self): - """ - This test shouldn't run in proliferated repositories. - """ - setup_cfg = configparser.ConfigParser() - setup_cfg.read("setup.cfg") - if setup_cfg["metadata"]["name"] != "skeleton": - return - - args = "venv/bin/black --check -l 100 setup.py etc tests" - try: - subprocess.check_output(args.split()) - except subprocess.CalledProcessError as e: - print("===========================================================") - print(e.output) - print("===========================================================") - raise Exception( - "Black style check failed; please format the code using:\n" - " python -m black -l 100 setup.py etc tests", - e.output, - ) from e diff --git a/packagedb/tests/packagedb/tests/testfiles/ant-commons-logging-1.6.1.jar b/packagedb/tests/testfiles/ant-commons-logging-1.6.1.jar similarity index 100% rename from packagedb/tests/packagedb/tests/testfiles/ant-commons-logging-1.6.1.jar rename to packagedb/tests/testfiles/ant-commons-logging-1.6.1.jar diff --git a/packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.0.RELEASE.jar b/packagedb/tests/testfiles/index/spring-boot-1.3.0.RELEASE.jar similarity index 100% rename from packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.0.RELEASE.jar rename to packagedb/tests/testfiles/index/spring-boot-1.3.0.RELEASE.jar diff --git a/packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.1.RELEASE.jar b/packagedb/tests/testfiles/index/spring-boot-1.3.1.RELEASE.jar similarity index 100% rename from packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.1.RELEASE.jar rename to packagedb/tests/testfiles/index/spring-boot-1.3.1.RELEASE.jar diff --git a/packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.2.RELEASE.jar b/packagedb/tests/testfiles/index/spring-boot-1.3.2.RELEASE.jar similarity index 100% rename from packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.2.RELEASE.jar rename to packagedb/tests/testfiles/index/spring-boot-1.3.2.RELEASE.jar diff --git a/packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.3.RELEASE.jar b/packagedb/tests/testfiles/index/spring-boot-1.3.3.RELEASE.jar similarity index 100% rename from packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.3.RELEASE.jar rename to packagedb/tests/testfiles/index/spring-boot-1.3.3.RELEASE.jar diff --git a/packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.4.RELEASE.jar b/packagedb/tests/testfiles/index/spring-boot-1.3.4.RELEASE.jar similarity index 100% rename from packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.4.RELEASE.jar rename to packagedb/tests/testfiles/index/spring-boot-1.3.4.RELEASE.jar diff --git a/packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.5.RELEASE.jar b/packagedb/tests/testfiles/index/spring-boot-1.3.5.RELEASE.jar similarity index 100% rename from packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.5.RELEASE.jar rename to packagedb/tests/testfiles/index/spring-boot-1.3.5.RELEASE.jar diff --git a/packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.6.RELEASE.jar b/packagedb/tests/testfiles/index/spring-boot-1.3.6.RELEASE.jar similarity index 100% rename from packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.6.RELEASE.jar rename to packagedb/tests/testfiles/index/spring-boot-1.3.6.RELEASE.jar diff --git a/packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.7.RELEASE.jar b/packagedb/tests/testfiles/index/spring-boot-1.3.7.RELEASE.jar similarity index 100% rename from packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.7.RELEASE.jar rename to packagedb/tests/testfiles/index/spring-boot-1.3.7.RELEASE.jar diff --git a/packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.8.RELEASE.jar b/packagedb/tests/testfiles/index/spring-boot-1.3.8.RELEASE.jar similarity index 100% rename from packagedb/tests/packagedb/tests/testfiles/index/spring-boot-1.3.8.RELEASE.jar rename to packagedb/tests/testfiles/index/spring-boot-1.3.8.RELEASE.jar diff --git a/packagedb/tests/packagedb/tests/testfiles/match_jars/ant-commons-logging-1.6.1.jar b/packagedb/tests/testfiles/match_jars/ant-commons-logging-1.6.1.jar similarity index 100% rename from packagedb/tests/packagedb/tests/testfiles/match_jars/ant-commons-logging-1.6.1.jar rename to packagedb/tests/testfiles/match_jars/ant-commons-logging-1.6.1.jar diff --git a/packagedb/tests/packagedb/tests/testfiles/match_jars/spring-boot-1.3.8.RELEASE.jar b/packagedb/tests/testfiles/match_jars/spring-boot-1.3.8.RELEASE.jar similarity index 100% rename from packagedb/tests/packagedb/tests/testfiles/match_jars/spring-boot-1.3.8.RELEASE.jar rename to packagedb/tests/testfiles/match_jars/spring-boot-1.3.8.RELEASE.jar diff --git a/packagedb/src/packagedb/migrations/__init__.py b/purldb/__init__.py similarity index 100% rename from packagedb/src/packagedb/migrations/__init__.py rename to purldb/__init__.py diff --git a/matchcode/src/matchcodeio/settings.py b/purldb/settings.py similarity index 86% rename from matchcode/src/matchcodeio/settings.py rename to purldb/settings.py index cb5796d3..5194e8a3 100644 --- a/matchcode/src/matchcodeio/settings.py +++ b/purldb/settings.py @@ -14,12 +14,12 @@ PROJECT_DIR = Path(__file__).resolve().parent -ROOT_DIR = PROJECT_DIR.parent.parent +ROOT_DIR = PROJECT_DIR.parent # Environment -ENV_FILE = "/etc/matchcodeio/.env" +ENV_FILE = "/etc/purldb/.env" if not Path(ENV_FILE).exists(): ENV_FILE = ROOT_DIR / ".env" @@ -33,20 +33,20 @@ ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=[".localhost", "127.0.0.1", "[::1]"]) # SECURITY WARNING: do not run with debug turned on in production -DEBUG = env.bool("MATCHCODEIO_DEBUG", default=False) +DEBUG = env.bool("PURLDB_DEBUG", default=False) -MATCHCODEIO_REQUIRE_AUTHENTICATION = env.bool( - "MATCHCODEIO_REQUIRE_AUTHENTICATION", default=False +PURLDB_REQUIRE_AUTHENTICATION = env.bool( + "PURLDB_REQUIRE_AUTHENTICATION", default=False ) # SECURITY WARNING: do not run with debug turned on in production -DEBUG_TOOLBAR = env.bool("MATCHCODEIO_DEBUG_TOOLBAR", default=False) +DEBUG_TOOLBAR = env.bool("PURLDB_DEBUG_TOOLBAR", default=False) -MATCHCODEIO_PASSWORD_MIN_LENGTH = env.int("MATCHCODEIO_PASSWORD_MIN_LENGTH", default=14) +PURLDB_PASSWORD_MIN_LENGTH = env.int("PURLDB_PASSWORD_MIN_LENGTH", default=14) -# Matchcode.io +# PurlDB -MATCHCODEIO_LOG_LEVEL = env.str("MATCHCODEIO_LOG_LEVEL", "INFO") +PURLDB_LOG_LEVEL = env.str("PURLDB_LOG_LEVEL", "INFO") # Application definition @@ -81,9 +81,9 @@ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) -ROOT_URLCONF = 'matchcodeio.urls' +ROOT_URLCONF = 'purldb.urls' -WSGI_APPLICATION = "matchcodeio.wsgi.application" +WSGI_APPLICATION = "purldb.wsgi.application" # Database @@ -134,7 +134,7 @@ { "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", "OPTIONS": { - "min_length": MATCHCODEIO_PASSWORD_MIN_LENGTH, + "min_length": PURLDB_PASSWORD_MIN_LENGTH, }, }, { @@ -182,16 +182,16 @@ "loggers": { "scanpipe": { "handlers": ["null"] if IS_TESTS else ["console"], - "level": MATCHCODEIO_LOG_LEVEL, + "level": PURLDB_LOG_LEVEL, "propagate": False, }, "django": { "handlers": ["null"] if IS_TESTS else ["console"], "propagate": False, }, - # Set MATCHCODEIO_LOG_LEVEL=DEBUG to display all SQL queries in the console. + # Set PURLDB_LOG_LEVEL=DEBUG to display all SQL queries in the console. "django.db.backends": { - "level": MATCHCODEIO_LOG_LEVEL, + "level": PURLDB_LOG_LEVEL, }, }, } @@ -210,7 +210,7 @@ STATIC_URL = '/static/' -STATIC_ROOT = '/var/matchcodeio/static/' +STATIC_ROOT = '/var/purldb/static/' STATICFILES_DIRS = [ PROJECT_DIR / 'static', @@ -232,12 +232,12 @@ 'django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.SearchFilter', ), - 'DEFAULT_PAGINATION_CLASS': 'matchcode.api_custom.PageSizePagination', + 'DEFAULT_PAGINATION_CLASS': 'packagedb.api_custom.PageSizePagination', # Limit the load on the Database returning a small number of records by default. https://github.com/nexB/vulnerablecode/issues/819 "PAGE_SIZE": 10, } -if not MATCHCODEIO_REQUIRE_AUTHENTICATION: +if not PURLDB_REQUIRE_AUTHENTICATION: REST_FRAMEWORK["DEFAULT_PERMISSION_CLASSES"] = ( "rest_framework.permissions.AllowAny", ) @@ -267,3 +267,8 @@ INTERNAL_IPS = [ "127.0.0.1", ] + +# Active seeders: each active seeder class need to be added explictly here +ACTIVE_SEEDERS = [ + 'discovery.visitors.npm.NpmSeed', +] diff --git a/matchcode/src/matchcodeio/static/.keep b/purldb/static/.keep similarity index 100% rename from matchcode/src/matchcodeio/static/.keep rename to purldb/static/.keep diff --git a/matchcode/src/matchcodeio/urls.py b/purldb/urls.py similarity index 100% rename from matchcode/src/matchcodeio/urls.py rename to purldb/urls.py diff --git a/matchcode/src/matchcodeio/wsgi.py b/purldb/wsgi.py similarity index 100% rename from matchcode/src/matchcodeio/wsgi.py rename to purldb/wsgi.py diff --git a/matchcode/pyproject.toml b/pyproject.toml similarity index 100% rename from matchcode/pyproject.toml rename to pyproject.toml diff --git a/matchcode/requirements-dev.txt b/requirements-dev.txt similarity index 100% rename from matchcode/requirements-dev.txt rename to requirements-dev.txt diff --git a/matchcode/requirements.txt b/requirements.txt similarity index 100% rename from matchcode/requirements.txt rename to requirements.txt diff --git a/matchcode/setup.cfg b/setup.cfg similarity index 76% rename from matchcode/setup.cfg rename to setup.cfg index 65198076..0bab5b55 100644 --- a/matchcode/setup.cfg +++ b/setup.cfg @@ -1,18 +1,19 @@ [metadata] +name = purldb +version = 2.0.0 license_files = LICENSE AUTHORS.rst CHANGELOG.rst -name = matchcode author = nexB. Inc. and others author_email = info@aboutcode.org -license = nexB Proprietary-license +license = Apache-2.0 AND CC-BY-SA-4.0 # description must be on ONE line https://github.com/pypa/setuptools/issues/1390 description = MatchCode long_description = file:README.rst long_description_content_type = text/x-rst -url = https://github.com/nexB/matchcode +url = https://github.com/nexB/purldb classifiers = Intended Audience :: Developers @@ -22,25 +23,39 @@ classifiers = keywords = matchcode + packagedb + scancode + purl + purldb + [options] -package_dir = - =src packages = find: include_package_data = true zip_safe = false install_requires = + arrow==1.2.3 bitarray == 2.6.0 clearcode-toolkit==0.0.3 + debian-inspector==31.0.0 commoncode == 31.0.0 Django == 4.1.2 django-environ==0.9.0 djangorestframework == 3.14.0 django-filter == 22.1 + ftputil==5.0.4 + jawa==2.2.0 minecode == 2.0.0 + natsort>=8.2.0 packagedb == 2.0.0 + packageurl-python>=0.10.4 + psycopg2-binary==2.9.3 psycopg2 == 2.9.3 + PyGithub==1.56 + reppy2==0.3.6 + rubymarshal==1.0.3 scancode-toolkit == 31.2.2 + urlpy==0.5 setup_requires = setuptools_scm[toml] >= 4 python_requires = >=3.8.* @@ -55,6 +70,7 @@ testing = pytest-django aboutcode-toolkit >= 6.0.0 black + mock docs= Sphinx>=3.3.1 diff --git a/matchcode/setup.py b/setup.py similarity index 100% rename from matchcode/setup.py rename to setup.py From cdfcb78ccc1a03df1c84d2fd0d69368486579091 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Wed, 14 Dec 2022 21:16:46 +0000 Subject: [PATCH 19/38] Convert clearcode to regular django app * Move test files Signed-off-by: Jono Yang --- clearcode-toolkit/.gitignore | 58 ----- clearcode-toolkit/NOTICE | 19 -- clearcode-toolkit/apache-2.0.LICENSE | 202 ------------------ clearcode-toolkit/configure | 7 - clearcode-toolkit/createdb.sh | 10 - clearcode-toolkit/devops-requirements.in | 1 - clearcode-toolkit/devops-requirements.txt | 14 -- clearcode-toolkit/etc/ansible/README.rst | 46 ---- clearcode-toolkit/etc/ansible/hosts | 2 - .../etc/ansible/roles/common/tasks/main.yml | 31 --- .../etc/ansible/roles/postgres/tasks/main.yml | 77 ------- .../etc/ansible/roles/project/tasks/main.yml | 92 -------- clearcode-toolkit/etc/ansible/site.yml | 31 --- clearcode-toolkit/etc/ansible/vars.yml | 35 --- clearcode-toolkit/manage.py | 10 - clearcode-toolkit/requirements.txt | 19 -- clearcode-toolkit/setup.cfg | 50 ----- clearcode-toolkit/setup.py | 65 ------ clearcode-toolkit/src/clearcode/dbconf.py | 68 ------ clearcode-toolkit/src/clearcode/dbsettings.py | 115 ---------- .../CHANGELOG.rst | 0 {clearcode-toolkit => clearcode}/README.rst | 0 .../src/clearcode => clearcode}/__init__.py | 0 .../src/clearcode => clearcode}/api.py | 0 .../src/clearcode => clearcode}/cdutils.py | 0 .../src/clearcode => clearcode}/load.py | 14 +- .../migrations/0001_initial.py | 0 .../migrations/0002_auto_20200331_1052.py | 0 .../migrations/0003_cditem_uuid.py | 0 .../migrations/__init__.py | 0 .../src/clearcode => clearcode}/models.py | 0 .../src/clearcode => clearcode}/sync.py | 7 - .../clearcode => clearcode}/tests/__init__.py | 0 .../clearcode => clearcode}/tests/test_api.py | 0 .../tests/test_models.py | 0 .../tests/test_sync.py | 0 configure | 6 +- .../ClearCode Introduction-June 2020.odp | Bin .../ClearCode Introduction-June 2020.pdf | Bin .../scripts/clearcode-api-backup.py | 0 .../scripts/clearcode-api-import.py | 0 matchcode/tests/matchcode/__init__.py | 0 matchcode/tests/{matchcode => }/test_api.py | 0 .../{matchcode => }/test_fingerprinting.py | 0 .../{matchcode => }/test_index_packages.py | 0 matchcode/tests/{matchcode => }/test_match.py | 0 .../tests/{matchcode => }/test_models.py | 0 matchcode/tests/test_skeleton_codestyle.py | 36 ---- .../testfiles/api/scan2_match_results.json | 0 .../fingerprinting/abbrev-1.0.3-i.json | 0 .../abbrev-1.0.3-i-expected.json | 0 .../directory-matching/abbrev-1.0.3-i.json | 0 .../abbrev-1.0.4-i-expected.json | 0 .../directory-matching/abbrev-1.0.4-i.json | 0 .../abbrev-1.0.5-i-expected.json | 0 .../directory-matching/abbrev-1.0.5-i.json | 0 .../abbrev-1.0.6-i-expected.json | 0 .../directory-matching/abbrev-1.0.6-i.json | 0 .../abbrev-1.0.7-i-expected.json | 0 .../directory-matching/abbrev-1.0.7-i.json | 0 .../abbrev-1.0.9-i-expected.json | 0 .../directory-matching/abbrev-1.0.9-i.json | 0 .../abbrev-1.1.0-i-expected.json | 0 .../directory-matching/abbrev-1.1.0-i.json | 0 .../abbrev-1.1.1-i-expected.json | 0 .../directory-matching/abbrev-1.1.1-i.json | 0 .../get-stdin-3.0.2-i-expected.json | 0 .../directory-matching/get-stdin-3.0.2-i.json | 0 .../match/nested/nested-expected.json | 0 .../testfiles/match/nested/nested.json | 0 .../match/nested/new-nested-expected.json | 0 .../testfiles/match/nested/new-nested.json | 0 .../match/nested/plugin-request-2.4.1-ip.json | 0 .../match/nested/underscore-1.10.9-ip.json | 0 .../testfiles/match/nested/underscore.json | 0 .../testfiles/match/scan1.json | 0 .../async-0.2.10.tgz-i.json | 0 .../async-0.2.9-i-expected-content.json | 0 .../async-0.2.9-i-expected-structure.json | 0 .../directory-matching/async-0.2.9-i.json | 0 ...file-matching-standalone-test-results.json | 0 ...approximate-directory-content-results.json | 0 ...proximate-directory-structure-results.json | 0 .../models/match-test-exact-file-results.json | 0 .../match-test-exact-package-results.json | 0 .../testfiles/models/match-test.json | 0 purldb/settings.py | 6 +- purldb/urls.py | 2 + setup.cfg | 7 +- 89 files changed, 19 insertions(+), 1011 deletions(-) delete mode 100644 clearcode-toolkit/.gitignore delete mode 100644 clearcode-toolkit/NOTICE delete mode 100644 clearcode-toolkit/apache-2.0.LICENSE delete mode 100755 clearcode-toolkit/configure delete mode 100755 clearcode-toolkit/createdb.sh delete mode 100644 clearcode-toolkit/devops-requirements.in delete mode 100644 clearcode-toolkit/devops-requirements.txt delete mode 100644 clearcode-toolkit/etc/ansible/README.rst delete mode 100644 clearcode-toolkit/etc/ansible/hosts delete mode 100644 clearcode-toolkit/etc/ansible/roles/common/tasks/main.yml delete mode 100644 clearcode-toolkit/etc/ansible/roles/postgres/tasks/main.yml delete mode 100644 clearcode-toolkit/etc/ansible/roles/project/tasks/main.yml delete mode 100644 clearcode-toolkit/etc/ansible/site.yml delete mode 100644 clearcode-toolkit/etc/ansible/vars.yml delete mode 100755 clearcode-toolkit/manage.py delete mode 100644 clearcode-toolkit/requirements.txt delete mode 100644 clearcode-toolkit/setup.cfg delete mode 100644 clearcode-toolkit/setup.py delete mode 100644 clearcode-toolkit/src/clearcode/dbconf.py delete mode 100644 clearcode-toolkit/src/clearcode/dbsettings.py rename {clearcode-toolkit => clearcode}/CHANGELOG.rst (100%) rename {clearcode-toolkit => clearcode}/README.rst (100%) rename {clearcode-toolkit/src/clearcode => clearcode}/__init__.py (100%) rename {clearcode-toolkit/src/clearcode => clearcode}/api.py (100%) rename {clearcode-toolkit/src/clearcode => clearcode}/cdutils.py (100%) rename {clearcode-toolkit/src/clearcode => clearcode}/load.py (95%) rename {clearcode-toolkit/src/clearcode => clearcode}/migrations/0001_initial.py (100%) rename {clearcode-toolkit/src/clearcode => clearcode}/migrations/0002_auto_20200331_1052.py (100%) rename {clearcode-toolkit/src/clearcode => clearcode}/migrations/0003_cditem_uuid.py (100%) rename {clearcode-toolkit/src/clearcode => clearcode}/migrations/__init__.py (100%) rename {clearcode-toolkit/src/clearcode => clearcode}/models.py (100%) rename {clearcode-toolkit/src/clearcode => clearcode}/sync.py (98%) rename {clearcode-toolkit/src/clearcode => clearcode}/tests/__init__.py (100%) rename {clearcode-toolkit/src/clearcode => clearcode}/tests/test_api.py (100%) rename {clearcode-toolkit/src/clearcode => clearcode}/tests/test_models.py (100%) rename {clearcode-toolkit/src/clearcode => clearcode}/tests/test_sync.py (100%) rename {clearcode-toolkit/docs => docs}/ClearCode Introduction-June 2020.odp (100%) rename {clearcode-toolkit/docs => docs}/ClearCode Introduction-June 2020.pdf (100%) rename {clearcode-toolkit/etc => etc}/scripts/clearcode-api-backup.py (100%) rename {clearcode-toolkit/etc => etc}/scripts/clearcode-api-import.py (100%) delete mode 100644 matchcode/tests/matchcode/__init__.py rename matchcode/tests/{matchcode => }/test_api.py (100%) rename matchcode/tests/{matchcode => }/test_fingerprinting.py (100%) rename matchcode/tests/{matchcode => }/test_index_packages.py (100%) rename matchcode/tests/{matchcode => }/test_match.py (100%) rename matchcode/tests/{matchcode => }/test_models.py (100%) delete mode 100644 matchcode/tests/test_skeleton_codestyle.py rename matchcode/tests/{matchcode => }/testfiles/api/scan2_match_results.json (100%) rename matchcode/tests/{matchcode => }/testfiles/fingerprinting/abbrev-1.0.3-i.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.0.3-i-expected.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.0.3-i.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.0.4-i-expected.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.0.4-i.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.0.5-i-expected.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.0.5-i.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.0.6-i-expected.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.0.6-i.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.0.7-i-expected.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.0.7-i.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.0.9-i-expected.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.0.9-i.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.1.0-i-expected.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.1.0-i.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.1.1-i-expected.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/abbrev-1.1.1-i.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/get-stdin-3.0.2-i-expected.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/directory-matching/get-stdin-3.0.2-i.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/nested/nested-expected.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/nested/nested.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/nested/new-nested-expected.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/nested/new-nested.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/nested/plugin-request-2.4.1-ip.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/nested/underscore-1.10.9-ip.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/nested/underscore.json (100%) rename matchcode/tests/{matchcode => }/testfiles/match/scan1.json (100%) rename matchcode/tests/{matchcode => }/testfiles/models/directory-matching/async-0.2.10.tgz-i.json (100%) rename matchcode/tests/{matchcode => }/testfiles/models/directory-matching/async-0.2.9-i-expected-content.json (100%) rename matchcode/tests/{matchcode => }/testfiles/models/directory-matching/async-0.2.9-i-expected-structure.json (100%) rename matchcode/tests/{matchcode => }/testfiles/models/directory-matching/async-0.2.9-i.json (100%) rename matchcode/tests/{matchcode => }/testfiles/models/exact-file-matching-standalone-test-results.json (100%) rename matchcode/tests/{matchcode => }/testfiles/models/match-test-approximate-directory-content-results.json (100%) rename matchcode/tests/{matchcode => }/testfiles/models/match-test-approximate-directory-structure-results.json (100%) rename matchcode/tests/{matchcode => }/testfiles/models/match-test-exact-file-results.json (100%) rename matchcode/tests/{matchcode => }/testfiles/models/match-test-exact-package-results.json (100%) rename matchcode/tests/{matchcode => }/testfiles/models/match-test.json (100%) diff --git a/clearcode-toolkit/.gitignore b/clearcode-toolkit/.gitignore deleted file mode 100644 index a6088237..00000000 --- a/clearcode-toolkit/.gitignore +++ /dev/null @@ -1,58 +0,0 @@ -*.py[cod] - -# virtualenv and other misc bits -*.egg-info -/dist -/build -/bin -/lib -/lib64 -/scripts -/Scripts -/Lib -/pip-selfcheck.json -/tmp -.Python -/include -/Include -/local -*/local/* -pyvenv.cfg - -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.cache -.coverage -.coverage.* -nosetests.xml -htmlcov - -# Translations -*.mo - -# IDEs -.project -.pydevproject -.idea -org.eclipse.core.resources.prefs -.vscode - -# Sphinx -docs/_build - -.DS_Store -*~ -.*.sw[po] -.build -.ve -*.bak -/.cache/ - -/local/ -/share/ -/tcl/ -/.python-version -/lib64/ -/lib64/ diff --git a/clearcode-toolkit/NOTICE b/clearcode-toolkit/NOTICE deleted file mode 100644 index 3bf20fa9..00000000 --- a/clearcode-toolkit/NOTICE +++ /dev/null @@ -1,19 +0,0 @@ -Software license -================ - -Copyright (c) nexB Inc. and others. All rights reserved. - -ClearCode is a free software tool from nexB Inc. and others. -Visit https://github.com/nexB/clearcode-toolkit/ for support and download. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/clearcode-toolkit/apache-2.0.LICENSE b/clearcode-toolkit/apache-2.0.LICENSE deleted file mode 100644 index d6456956..00000000 --- a/clearcode-toolkit/apache-2.0.LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/clearcode-toolkit/configure b/clearcode-toolkit/configure deleted file mode 100755 index a22125d9..00000000 --- a/clearcode-toolkit/configure +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# -# Copyright (c) nexB Inc. and others. All rights reserved. - -python3 -m venv . -source bin/activate -pip install -e . diff --git a/clearcode-toolkit/createdb.sh b/clearcode-toolkit/createdb.sh deleted file mode 100755 index 94eb15ae..00000000 --- a/clearcode-toolkit/createdb.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# Copyright (c) nexB Inc. and others. All rights reserved. - -echo "-> Create the ClearCode database" - -# CREATEDB is required for clearcode in order to run tests in the future -sudo -u postgres psql < --ask-become-pass site.yml - - -Common operations ------------------ - - -Local testing in a VM -~~~~~~~~~~~~~~~~~~~~~ -For local testing in a VM, first install a bare VM with Ubuntu Serevr 16.X LTS. -You only need the OpenSSH server and need to be able to SSH in the VM. -This is typically achieved by using Bridge Networking in VirtualBox. - -- SSH in the VM to get its IP address with `$ ifconfig`. -- Given an IP of ww.xx.yy.zz, update the `hosts-test` sample host file with this local IP, - e.g. the IP of your VM in a [testvm] role - Do NOT use the `hosts` inventory file in you ansible commands. -- Then you can run ansible with this test config using your VM user password that will be - asked twice. You may need to have `sshpass` installed. Or you can setup SSH key auth - otherwise for password-less SSH'ing in the VM.:: - - ansible-playbook -i hosts-test ---verbose --ask-become-pass site.yml - -When testing you may want to locally change the vars.yml playbook to fetch -alternative test branches. diff --git a/clearcode-toolkit/etc/ansible/hosts b/clearcode-toolkit/etc/ansible/hosts deleted file mode 100644 index eed88973..00000000 --- a/clearcode-toolkit/etc/ansible/hosts +++ /dev/null @@ -1,2 +0,0 @@ -[server] -127.0.0.1:2222 diff --git a/clearcode-toolkit/etc/ansible/roles/common/tasks/main.yml b/clearcode-toolkit/etc/ansible/roles/common/tasks/main.yml deleted file mode 100644 index 14ae208e..00000000 --- a/clearcode-toolkit/etc/ansible/roles/common/tasks/main.yml +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# -# ClearCode is a free software tool from nexB Inc. and others. -# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -- name: Update and upgrade installed packages - apt: - update_cache: yes - upgrade: full - cache_valid_time: 600 - -- name: Install or update packages - apt: - name: "{{ packages }}" - state: latest - cache_valid_time: 600 - update_cache: yes diff --git a/clearcode-toolkit/etc/ansible/roles/postgres/tasks/main.yml b/clearcode-toolkit/etc/ansible/roles/postgres/tasks/main.yml deleted file mode 100644 index 8ba7918c..00000000 --- a/clearcode-toolkit/etc/ansible/roles/postgres/tasks/main.yml +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# -# ClearCode is a free software tool from nexB Inc. and others. -# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -- name: Install PostgreSQL packages - apt: - pkg: - - postgresql - - postgresql-server-dev-10 - - python-psycopg2 # required by ansible for the database setup - - python3.6-psycopg2 # required by ansible for the database setup - -- name: Ensure PostgreSQL service is started and enabled at boot - service: - name: postgresql - state: started - enabled: yes - -- name: Create database user - become: yes - become_user: postgres - postgresql_user: - name: "{{ project_name }}" - password: "cl34-u5er" # FIXME: Put this in a vault - role_attr_flags: NOCREATEROLE,NOSUPERUSER,CREATEDB,LOGIN,INHERIT - state: present - -- name: Create database - become: yes - become_user: postgres - postgresql_db: - name: "{{ project_name }}" - owner: "{{ project_name }}" - encoding: UTF-8 - template: template0 - lc_collate: en_US.UTF-8 - lc_ctype: en_US.UTF-8 - state: present - -- name: Restart PostgreSQL service - become: yes - become_user: root - service: - name: postgresql - state: restarted - - # TODO: impliment backup scheme (probably do not want this to be everyday, due to db size - #- name: Create project backups directory - # file: - # path: "/var/backups/{{ project_name }}" - # state: directory - # owner: postgres - # group: postgres - # - #- name: Setup daily database backup in the postgres user crontab - # cron: - # name: "Daily database backups at 01:00am" - # user: postgres - # minute: "0" - # hour: "1" - # job: 'pg_dump -Fc clearcode > /var/backups/clearcode/clearcode_`date "+\%Y-\%m-\%d_\%H\%M"`.dump' - # diff --git a/clearcode-toolkit/etc/ansible/roles/project/tasks/main.yml b/clearcode-toolkit/etc/ansible/roles/project/tasks/main.yml deleted file mode 100644 index ff4fa3e3..00000000 --- a/clearcode-toolkit/etc/ansible/roles/project/tasks/main.yml +++ /dev/null @@ -1,92 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# -# ClearCode is a free software tool from nexB Inc. and others. -# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -- name: Create project group - group: - name: "{{ project_name }}" - state: present - -- name: Create project user - user: - name: "{{ project_name }}" - group: "{{ project_name }}" - state: present - create_home: no - shell: /bin/bash - -# Copy the public key `ssh_public_key` value from the ansible output -# into the github deploy keys at https://github.com/nexB/scancode.io/settings/keys -# Title: scancodeio@.scancode.io -- name: Create a 4096-bit RSA SSH key for project user in .ssh/id_rsa - user: - name: "{{ project_name }}" - generate_ssh_key: yes - ssh_key_type: rsa - ssh_key_bits: 4096 - ssh_key_file: .ssh/id_rsa - -- name: Create project /opt directory - file: - path: "/opt/{{ project_name }}" - state: directory - owner: "{{ project_name }}" - group: "{{ project_name }}" - -- name: Create project /var directory - file: - path: "/var/{{ project_name }}" - state: directory - owner: "{{ project_name }}" - group: "{{ project_name }}" - -- name: Create project /etc directory - file: - path: "/etc/{{ project_name }}" - state: directory - owner: "{{ project_name }}" - group: "{{ project_name }}" - -- name: Fetch project git repository - become: true - become_user: "{{ project_name }}" - become_method: sudo - git: - repo: "{{ project_repo }}" - dest: "/opt/{{ project_name }}" - version: "{{ git_branch }}" - accept_hostkey: true - -- name: Ensure everything is owned by project user + owner - become: true - become_user: root - become_method: sudo - file: - path: "/opt/{{ project_name }}" - state: directory - owner: "{{ project_name }}" - group: "{{ project_name }}" - recurse: true - -- name: Configure the project - become: true - become_user: "{{ project_name }}" - become_method: sudo - command: ./configure - args: - chdir: "/opt/{{ project_name }}/" diff --git a/clearcode-toolkit/etc/ansible/site.yml b/clearcode-toolkit/etc/ansible/site.yml deleted file mode 100644 index c65ee076..00000000 --- a/clearcode-toolkit/etc/ansible/site.yml +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# -# ClearCode is a free software tool from nexB Inc. and others. -# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -- name: Deploy clearcode server - hosts: all - become: yes - become_user: root - become_method: sudo - vars_files: - - vars.yml - - roles: - - common - - postgres - - project diff --git a/clearcode-toolkit/etc/ansible/vars.yml b/clearcode-toolkit/etc/ansible/vars.yml deleted file mode 100644 index 754bed62..00000000 --- a/clearcode-toolkit/etc/ansible/vars.yml +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# -# ClearCode is a free software tool from nexB Inc. and others. -# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -hostname: clearcode -project_name: clearcode -project_home: /opt/clearcode -project_repo: ssh://git@github.com/nexB/clearcode-toolkit.git - -git_branch: master - -packages: - - git - - python3-dev - - python3-venv - - postgresql - - postgresql-server-dev-10 - - gcc - - screen - - fail2ban diff --git a/clearcode-toolkit/manage.py b/clearcode-toolkit/manage.py deleted file mode 100755 index 48912909..00000000 --- a/clearcode-toolkit/manage.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "clearcode.dbsettings") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff --git a/clearcode-toolkit/requirements.txt b/clearcode-toolkit/requirements.txt deleted file mode 100644 index 7b775876..00000000 --- a/clearcode-toolkit/requirements.txt +++ /dev/null @@ -1,19 +0,0 @@ -# -# This file is autogenerated by pip-compile -# To update, run: -# -# pip-compile -# -asgiref==3.2.7 # via django -attrs==19.3.0 # via clearcode-toolkit (setup.py) -certifi==2020.4.5.1 # via requests -chardet==3.0.4 # via requests -click==7.1.1 # via clearcode-toolkit (setup.py) -django==3.0.7 # via clearcode-toolkit (setup.py), djangorestframework -djangorestframework==3.11.0 # via clearcode-toolkit (setup.py) -idna==2.9 # via requests -psycopg2==2.8.5 # via clearcode-toolkit (setup.py) -pytz==2019.3 # via django -requests==2.23.0 # via clearcode-toolkit (setup.py) -sqlparse==0.3.1 # via django -urllib3==1.25.9 # via requests diff --git a/clearcode-toolkit/setup.cfg b/clearcode-toolkit/setup.cfg deleted file mode 100644 index d4bbcac4..00000000 --- a/clearcode-toolkit/setup.cfg +++ /dev/null @@ -1,50 +0,0 @@ -[metadata] -license_files = - NOTICE - apache-2.0.LICENSE - README.rst - - -[aliases] -release = clean --all sdist bdist_wheel - - -[tool:pytest] -norecursedirs = - .git - bin - dist - build - _build - dist - etc - local - ci - docs - man - share - samples - .cache - .settings - Include - include - Lib - lib - lib64 - Lib64 - Scripts - thirdparty - tmp - src/*/data - tests/*/data - -python_files = *.py - -python_classes=Test -python_functions=test - -addopts = - -rfExXw - --strict - --ignore setup.py - --doctest-modules diff --git a/clearcode-toolkit/setup.py b/clearcode-toolkit/setup.py deleted file mode 100644 index 874f90fe..00000000 --- a/clearcode-toolkit/setup.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- - -from __future__ import absolute_import -from __future__ import print_function - -import io -from glob import glob -from os.path import basename -from os.path import dirname -from os.path import join -from os.path import splitext -import re - -from setuptools import find_packages -from setuptools import setup - - -setup( - name='clearcode-toolkit', - version='0.0.3', - license='Apache-2.0', - description='ClearCode is a tool to sync ClearlyDefined data.', - long_description='ClearCode is a tool to sync ClearlyDefined scans and curations.', - author='nexB Inc. and others', - author_email='info@aboutcode.org', - url='https://github.com/nexB/clearcode-toolkit', - packages=find_packages('src'), - package_dir={'': 'src'}, - py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')], - include_package_data=True, - zip_safe=False, - classifiers=[ - # complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Topic :: Utilities', - ], - keywords=[ - 'open source', - 'scan', - 'license', - 'package', - 'clearlydefined', - 'scancode', - ], - install_requires=[ - 'attrs', - 'click', - 'django', - 'psycopg2', - 'requests', - 'djangorestframework', - 'packageurl-python', - ], - entry_points={ - 'console_scripts': [ - 'clearsync = clearcode.sync:cli', - 'clearload = clearcode.load:cli', - ], - }, -) diff --git a/clearcode-toolkit/src/clearcode/dbconf.py b/clearcode-toolkit/src/clearcode/dbconf.py deleted file mode 100644 index d7e3565c..00000000 --- a/clearcode-toolkit/src/clearcode/dbconf.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# -# ClearCode is a free software tool from nexB Inc. and others. -# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import django -from django.apps import apps -from django.conf import settings - -from clearcode import dbsettings - - -""" -Configuration helpers for the embedded sqlite DB used through Django's ORM. -You must call configure() before calling or importing anything else from ScanCode. - -WARNING: DO NOT USE THESE IF YOU WANT TO USE SCANCODE IN A REGULAR DJANGO WEB APP. -THE SETTINGS DEFINED HERE ARE ONLY FOR USING SCANCODE AS A COMMAND LINE OR WHEN USING -SCANCODE AS A LIBRARY OUTSIDE OF A DJANGO WEB APPLICATION. -""" - - -def configure(settings_module=dbsettings, verbose=False): - """ - Configure minimally Django (in particular the ORM and DB) using the - `settings_modules` module. When using as a library you must call this - function at least once before calling other code. - """ - if not settings.configured: - settings.configure(**get_settings(settings_module)) - # call django.setup() only once - if not apps.ready: - django.setup() - create_db(verbose=verbose) - - -def get_settings(settings_module=dbsettings): - """ - Return a mapping of UPPERCASE settings from a module - """ - # settings are all UPPERCASE - sets = [s for s in dir(dbsettings) if s.isupper()] - return {s: getattr(dbsettings, s) for s in sets} - - -def create_db(verbose=False, _already_created=[]): - """ - Create the database by invoking the migrate command behind the scenes. - """ - if not _already_created: - verbosity = 2 if verbose else 0 - from django.core.management import call_command - call_command('migrate', verbosity=verbosity, interactive=False, run_syncdb=True) - _already_created.append(True) diff --git a/clearcode-toolkit/src/clearcode/dbsettings.py b/clearcode-toolkit/src/clearcode/dbsettings.py deleted file mode 100644 index 6ed40c46..00000000 --- a/clearcode-toolkit/src/clearcode/dbsettings.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# -# ClearCode is a free software tool from nexB Inc. and others. -# Visit https://github.com/nexB/clearcode-toolkit/ for support and download. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Django Settings for ClearCode DB. -""" - -DATABASES = dict( - default=dict( - ENGINE='django.db.backends.postgresql', - HOST='localhost', - PORT='5432', - NAME='packagedb', - USER='packagedb', - PASSWORD='packagedb', - ATOMIC_REQUESTS=True, - ) -) - -# TODO: Add some sort of api auth if you expose to the world -ALLOWED_HOSTS = ['localhost', '127.0.0.1'] - -ROOT_URLCONF = 'clearcode.api' - -# To disable running migration on each run, enable a fake migration below -# class FakeMigrations(dict): -# """ -# Fake migration class to avoid running migrations at all. For the CLI -# usage, a new DB is created on each run, so running migrations has no value. -# inspired by https://github.com/henriquebastos/django-test-without-migrations -# """ -# def __getitem__(self, item): -# return 'do not run any migrations' -# -# def __contains__(self, item): -# return True -# MIGRATION_MODULES = dbconf.FakeMigrations() - -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'clearcode', - 'rest_framework', -] - -MIDDLEWARE = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) - -REST_FRAMEWORK = { - 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', - 'PAGE_SIZE': 100, - 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.AllowAny', - ], - 'DEFAULT_RENDERER_CLASSES': [ - 'rest_framework.renderers.JSONRenderer', - ], -} - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - } - } -] - -# SECURITY WARNING: keep the secret key used in a production webapp! -# here we are running a local CLI-only application and do not need a Django secret -SECRET_KEY = '# SECURITY WARNING: keep the secret key used in a production webapp!' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = False - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# On Unix systems, a value of None will cause Django to use the same -# timezone as the operating system. -TIME_ZONE = 'UTC' - -STATIC_URL = '/static/' - diff --git a/clearcode-toolkit/CHANGELOG.rst b/clearcode/CHANGELOG.rst similarity index 100% rename from clearcode-toolkit/CHANGELOG.rst rename to clearcode/CHANGELOG.rst diff --git a/clearcode-toolkit/README.rst b/clearcode/README.rst similarity index 100% rename from clearcode-toolkit/README.rst rename to clearcode/README.rst diff --git a/clearcode-toolkit/src/clearcode/__init__.py b/clearcode/__init__.py similarity index 100% rename from clearcode-toolkit/src/clearcode/__init__.py rename to clearcode/__init__.py diff --git a/clearcode-toolkit/src/clearcode/api.py b/clearcode/api.py similarity index 100% rename from clearcode-toolkit/src/clearcode/api.py rename to clearcode/api.py diff --git a/clearcode-toolkit/src/clearcode/cdutils.py b/clearcode/cdutils.py similarity index 100% rename from clearcode-toolkit/src/clearcode/cdutils.py rename to clearcode/cdutils.py diff --git a/clearcode-toolkit/src/clearcode/load.py b/clearcode/load.py similarity index 95% rename from clearcode-toolkit/src/clearcode/load.py rename to clearcode/load.py index 56de2b4a..8dff398b 100644 --- a/clearcode-toolkit/src/clearcode/load.py +++ b/clearcode/load.py @@ -32,7 +32,7 @@ Operation --------- This script walks a given `--input-dir` location and loads any ClearlyDefined data -into a Database (currently postgreSQL). +into a Database (currently postgreSQL). Usage ----- @@ -52,8 +52,6 @@ def walk_and_load_from_filesystem(input_dir, cd_root_dir): CDitem.path = npm/npmjs/@actions/github/revision/2.1.1.json.gz CDitem.content = 'the file: 2.1.1.json.gz in bytes' """ - from clearcode import dbconf - dbconf.configure() # for now, we count dirs too file_counter = 1 @@ -67,13 +65,13 @@ def walk_and_load_from_filesystem(input_dir, cd_root_dir): # TODO: check if the location is actually a CD data item. full_gzip_path = os.path.join(root, filename) full_json_path = full_gzip_path.rstrip('.gz') - + # normalize the `path` value by removing the arbitrary parent directory cditem_rel_path = os.path.relpath(full_json_path, cd_root_dir) - + with open(full_gzip_path, mode='rb') as f: content = f.read() - + from clearcode import models # Save to DB try: @@ -97,8 +95,8 @@ def walk_and_load_from_filesystem(input_dir, cd_root_dir): def cli(input_dir=None, cd_root_dir=None, *arg, **kwargs): """ - Handle ClearlyDefined gzipped JSON scans by walking a clearsync directory structure, - creating CDItem objects and loading them into a PostgreSQL database. + Handle ClearlyDefined gzipped JSON scans by walking a clearsync directory structure, + creating CDItem objects and loading them into a PostgreSQL database. """ if not input_dir: sys.exit('Please specify an input directory using the `--input-dir` option.') diff --git a/clearcode-toolkit/src/clearcode/migrations/0001_initial.py b/clearcode/migrations/0001_initial.py similarity index 100% rename from clearcode-toolkit/src/clearcode/migrations/0001_initial.py rename to clearcode/migrations/0001_initial.py diff --git a/clearcode-toolkit/src/clearcode/migrations/0002_auto_20200331_1052.py b/clearcode/migrations/0002_auto_20200331_1052.py similarity index 100% rename from clearcode-toolkit/src/clearcode/migrations/0002_auto_20200331_1052.py rename to clearcode/migrations/0002_auto_20200331_1052.py diff --git a/clearcode-toolkit/src/clearcode/migrations/0003_cditem_uuid.py b/clearcode/migrations/0003_cditem_uuid.py similarity index 100% rename from clearcode-toolkit/src/clearcode/migrations/0003_cditem_uuid.py rename to clearcode/migrations/0003_cditem_uuid.py diff --git a/clearcode-toolkit/src/clearcode/migrations/__init__.py b/clearcode/migrations/__init__.py similarity index 100% rename from clearcode-toolkit/src/clearcode/migrations/__init__.py rename to clearcode/migrations/__init__.py diff --git a/clearcode-toolkit/src/clearcode/models.py b/clearcode/models.py similarity index 100% rename from clearcode-toolkit/src/clearcode/models.py rename to clearcode/models.py diff --git a/clearcode-toolkit/src/clearcode/sync.py b/clearcode/sync.py similarity index 98% rename from clearcode-toolkit/src/clearcode/sync.py rename to clearcode/sync.py index b744da86..8b7f3d8e 100644 --- a/clearcode-toolkit/src/clearcode/sync.py +++ b/clearcode/sync.py @@ -99,10 +99,6 @@ def fetch_and_save_latest_definitions( """ assert output_dir or save_to_db, 'You must select one of the --output-dir or --save-to-db options.' - if save_to_db: - from clearcode import dbconf - dbconf.configure(verbose=verbose) - definitions_url = cdutils.append_path_to_url(base_api_url, extra_path='definitions') if by_latest: definitions_url = cdutils.update_url(definitions_url, qs_mapping=dict(sort='releaseDate', sortDesc='true')) @@ -280,9 +276,6 @@ def fetch_and_save_harvests( (Note: Return a tuple of (etag, md5, url) for usage as a callback) """ assert output_dir or save_to_db, 'You must select one of the --output-dir or --save-to-db options.' - if save_to_db: - from clearcode import dbconf - dbconf.configure(verbose=verbose) url = coordinate.get_harvests_api_url() etag, checksum, content = cache.get_content( diff --git a/clearcode-toolkit/src/clearcode/tests/__init__.py b/clearcode/tests/__init__.py similarity index 100% rename from clearcode-toolkit/src/clearcode/tests/__init__.py rename to clearcode/tests/__init__.py diff --git a/clearcode-toolkit/src/clearcode/tests/test_api.py b/clearcode/tests/test_api.py similarity index 100% rename from clearcode-toolkit/src/clearcode/tests/test_api.py rename to clearcode/tests/test_api.py diff --git a/clearcode-toolkit/src/clearcode/tests/test_models.py b/clearcode/tests/test_models.py similarity index 100% rename from clearcode-toolkit/src/clearcode/tests/test_models.py rename to clearcode/tests/test_models.py diff --git a/clearcode-toolkit/src/clearcode/tests/test_sync.py b/clearcode/tests/test_sync.py similarity index 100% rename from clearcode-toolkit/src/clearcode/tests/test_sync.py rename to clearcode/tests/test_sync.py diff --git a/configure b/configure index 006e891a..de5c2083 100755 --- a/configure +++ b/configure @@ -30,9 +30,9 @@ CLI_ARGS=$1 CUSTOM_PACKAGES="https://github.com/nexB/commoncode/archive/refs/heads/48-correctly-assign-codebase-attributes.zip https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip" # Requirement arguments passed to pip and used by default or with --dev. -REQUIREMENTS="$CUSTOM_PACKAGES --editable ./clearcode-toolkit --editable . --constraint requirements.txt" -DEV_REQUIREMENTS="$CUSTOM_PACKAGES --editable ./clearcode-toolkit --editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt" -DOCS_REQUIREMENTS="$CUSTOM_PACKAGES --editable ./clearcode-toolkit --editable .[docs] --constraint requirements.txt" +REQUIREMENTS="$CUSTOM_PACKAGES --editable . --constraint requirements.txt" +DEV_REQUIREMENTS="$CUSTOM_PACKAGES --editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt" +DOCS_REQUIREMENTS="$CUSTOM_PACKAGES --editable .[docs] --constraint requirements.txt" # where we create a virtualenv VIRTUALENV_DIR=venv diff --git a/clearcode-toolkit/docs/ClearCode Introduction-June 2020.odp b/docs/ClearCode Introduction-June 2020.odp similarity index 100% rename from clearcode-toolkit/docs/ClearCode Introduction-June 2020.odp rename to docs/ClearCode Introduction-June 2020.odp diff --git a/clearcode-toolkit/docs/ClearCode Introduction-June 2020.pdf b/docs/ClearCode Introduction-June 2020.pdf similarity index 100% rename from clearcode-toolkit/docs/ClearCode Introduction-June 2020.pdf rename to docs/ClearCode Introduction-June 2020.pdf diff --git a/clearcode-toolkit/etc/scripts/clearcode-api-backup.py b/etc/scripts/clearcode-api-backup.py similarity index 100% rename from clearcode-toolkit/etc/scripts/clearcode-api-backup.py rename to etc/scripts/clearcode-api-backup.py diff --git a/clearcode-toolkit/etc/scripts/clearcode-api-import.py b/etc/scripts/clearcode-api-import.py similarity index 100% rename from clearcode-toolkit/etc/scripts/clearcode-api-import.py rename to etc/scripts/clearcode-api-import.py diff --git a/matchcode/tests/matchcode/__init__.py b/matchcode/tests/matchcode/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/matchcode/tests/matchcode/test_api.py b/matchcode/tests/test_api.py similarity index 100% rename from matchcode/tests/matchcode/test_api.py rename to matchcode/tests/test_api.py diff --git a/matchcode/tests/matchcode/test_fingerprinting.py b/matchcode/tests/test_fingerprinting.py similarity index 100% rename from matchcode/tests/matchcode/test_fingerprinting.py rename to matchcode/tests/test_fingerprinting.py diff --git a/matchcode/tests/matchcode/test_index_packages.py b/matchcode/tests/test_index_packages.py similarity index 100% rename from matchcode/tests/matchcode/test_index_packages.py rename to matchcode/tests/test_index_packages.py diff --git a/matchcode/tests/matchcode/test_match.py b/matchcode/tests/test_match.py similarity index 100% rename from matchcode/tests/matchcode/test_match.py rename to matchcode/tests/test_match.py diff --git a/matchcode/tests/matchcode/test_models.py b/matchcode/tests/test_models.py similarity index 100% rename from matchcode/tests/matchcode/test_models.py rename to matchcode/tests/test_models.py diff --git a/matchcode/tests/test_skeleton_codestyle.py b/matchcode/tests/test_skeleton_codestyle.py deleted file mode 100644 index 2eb6e558..00000000 --- a/matchcode/tests/test_skeleton_codestyle.py +++ /dev/null @@ -1,36 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# ScanCode is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/skeleton for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -import subprocess -import unittest -import configparser - - -class BaseTests(unittest.TestCase): - def test_skeleton_codestyle(self): - """ - This test shouldn't run in proliferated repositories. - """ - setup_cfg = configparser.ConfigParser() - setup_cfg.read("setup.cfg") - if setup_cfg["metadata"]["name"] != "skeleton": - return - - args = "venv/bin/black --check -l 100 setup.py etc tests" - try: - subprocess.check_output(args.split()) - except subprocess.CalledProcessError as e: - print("===========================================================") - print(e.output) - print("===========================================================") - raise Exception( - "Black style check failed; please format the code using:\n" - " python -m black -l 100 setup.py etc tests", - e.output, - ) from e diff --git a/matchcode/tests/matchcode/testfiles/api/scan2_match_results.json b/matchcode/tests/testfiles/api/scan2_match_results.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/api/scan2_match_results.json rename to matchcode/tests/testfiles/api/scan2_match_results.json diff --git a/matchcode/tests/matchcode/testfiles/fingerprinting/abbrev-1.0.3-i.json b/matchcode/tests/testfiles/fingerprinting/abbrev-1.0.3-i.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/fingerprinting/abbrev-1.0.3-i.json rename to matchcode/tests/testfiles/fingerprinting/abbrev-1.0.3-i.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.3-i-expected.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.3-i-expected.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.3-i-expected.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.3-i-expected.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.3-i.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.3-i.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.3-i.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.3-i.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.4-i-expected.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.4-i-expected.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.4-i-expected.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.4-i-expected.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.4-i.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.4-i.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.4-i.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.4-i.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.5-i-expected.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.5-i-expected.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.5-i-expected.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.5-i-expected.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.5-i.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.5-i.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.5-i.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.5-i.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.6-i-expected.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.6-i-expected.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.6-i-expected.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.6-i-expected.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.6-i.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.6-i.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.6-i.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.6-i.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.7-i-expected.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.7-i-expected.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.7-i-expected.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.7-i-expected.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.7-i.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.7-i.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.7-i.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.7-i.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.9-i-expected.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.9-i-expected.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.9-i-expected.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.9-i-expected.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.9-i.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.9-i.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.0.9-i.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.0.9-i.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.0-i-expected.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.1.0-i-expected.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.0-i-expected.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.1.0-i-expected.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.0-i.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.1.0-i.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.0-i.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.1.0-i.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.1-i-expected.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.1.1-i-expected.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.1-i-expected.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.1.1-i-expected.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.1-i.json b/matchcode/tests/testfiles/match/directory-matching/abbrev-1.1.1-i.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/abbrev-1.1.1-i.json rename to matchcode/tests/testfiles/match/directory-matching/abbrev-1.1.1-i.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/get-stdin-3.0.2-i-expected.json b/matchcode/tests/testfiles/match/directory-matching/get-stdin-3.0.2-i-expected.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/get-stdin-3.0.2-i-expected.json rename to matchcode/tests/testfiles/match/directory-matching/get-stdin-3.0.2-i-expected.json diff --git a/matchcode/tests/matchcode/testfiles/match/directory-matching/get-stdin-3.0.2-i.json b/matchcode/tests/testfiles/match/directory-matching/get-stdin-3.0.2-i.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/directory-matching/get-stdin-3.0.2-i.json rename to matchcode/tests/testfiles/match/directory-matching/get-stdin-3.0.2-i.json diff --git a/matchcode/tests/matchcode/testfiles/match/nested/nested-expected.json b/matchcode/tests/testfiles/match/nested/nested-expected.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/nested/nested-expected.json rename to matchcode/tests/testfiles/match/nested/nested-expected.json diff --git a/matchcode/tests/matchcode/testfiles/match/nested/nested.json b/matchcode/tests/testfiles/match/nested/nested.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/nested/nested.json rename to matchcode/tests/testfiles/match/nested/nested.json diff --git a/matchcode/tests/matchcode/testfiles/match/nested/new-nested-expected.json b/matchcode/tests/testfiles/match/nested/new-nested-expected.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/nested/new-nested-expected.json rename to matchcode/tests/testfiles/match/nested/new-nested-expected.json diff --git a/matchcode/tests/matchcode/testfiles/match/nested/new-nested.json b/matchcode/tests/testfiles/match/nested/new-nested.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/nested/new-nested.json rename to matchcode/tests/testfiles/match/nested/new-nested.json diff --git a/matchcode/tests/matchcode/testfiles/match/nested/plugin-request-2.4.1-ip.json b/matchcode/tests/testfiles/match/nested/plugin-request-2.4.1-ip.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/nested/plugin-request-2.4.1-ip.json rename to matchcode/tests/testfiles/match/nested/plugin-request-2.4.1-ip.json diff --git a/matchcode/tests/matchcode/testfiles/match/nested/underscore-1.10.9-ip.json b/matchcode/tests/testfiles/match/nested/underscore-1.10.9-ip.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/nested/underscore-1.10.9-ip.json rename to matchcode/tests/testfiles/match/nested/underscore-1.10.9-ip.json diff --git a/matchcode/tests/matchcode/testfiles/match/nested/underscore.json b/matchcode/tests/testfiles/match/nested/underscore.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/nested/underscore.json rename to matchcode/tests/testfiles/match/nested/underscore.json diff --git a/matchcode/tests/matchcode/testfiles/match/scan1.json b/matchcode/tests/testfiles/match/scan1.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/match/scan1.json rename to matchcode/tests/testfiles/match/scan1.json diff --git a/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.10.tgz-i.json b/matchcode/tests/testfiles/models/directory-matching/async-0.2.10.tgz-i.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.10.tgz-i.json rename to matchcode/tests/testfiles/models/directory-matching/async-0.2.10.tgz-i.json diff --git a/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i-expected-content.json b/matchcode/tests/testfiles/models/directory-matching/async-0.2.9-i-expected-content.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i-expected-content.json rename to matchcode/tests/testfiles/models/directory-matching/async-0.2.9-i-expected-content.json diff --git a/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i-expected-structure.json b/matchcode/tests/testfiles/models/directory-matching/async-0.2.9-i-expected-structure.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i-expected-structure.json rename to matchcode/tests/testfiles/models/directory-matching/async-0.2.9-i-expected-structure.json diff --git a/matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i.json b/matchcode/tests/testfiles/models/directory-matching/async-0.2.9-i.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/models/directory-matching/async-0.2.9-i.json rename to matchcode/tests/testfiles/models/directory-matching/async-0.2.9-i.json diff --git a/matchcode/tests/matchcode/testfiles/models/exact-file-matching-standalone-test-results.json b/matchcode/tests/testfiles/models/exact-file-matching-standalone-test-results.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/models/exact-file-matching-standalone-test-results.json rename to matchcode/tests/testfiles/models/exact-file-matching-standalone-test-results.json diff --git a/matchcode/tests/matchcode/testfiles/models/match-test-approximate-directory-content-results.json b/matchcode/tests/testfiles/models/match-test-approximate-directory-content-results.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/models/match-test-approximate-directory-content-results.json rename to matchcode/tests/testfiles/models/match-test-approximate-directory-content-results.json diff --git a/matchcode/tests/matchcode/testfiles/models/match-test-approximate-directory-structure-results.json b/matchcode/tests/testfiles/models/match-test-approximate-directory-structure-results.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/models/match-test-approximate-directory-structure-results.json rename to matchcode/tests/testfiles/models/match-test-approximate-directory-structure-results.json diff --git a/matchcode/tests/matchcode/testfiles/models/match-test-exact-file-results.json b/matchcode/tests/testfiles/models/match-test-exact-file-results.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/models/match-test-exact-file-results.json rename to matchcode/tests/testfiles/models/match-test-exact-file-results.json diff --git a/matchcode/tests/matchcode/testfiles/models/match-test-exact-package-results.json b/matchcode/tests/testfiles/models/match-test-exact-package-results.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/models/match-test-exact-package-results.json rename to matchcode/tests/testfiles/models/match-test-exact-package-results.json diff --git a/matchcode/tests/matchcode/testfiles/models/match-test.json b/matchcode/tests/testfiles/models/match-test.json similarity index 100% rename from matchcode/tests/matchcode/testfiles/models/match-test.json rename to matchcode/tests/testfiles/models/match-test.json diff --git a/purldb/settings.py b/purldb/settings.py index 5194e8a3..e3925a1e 100644 --- a/purldb/settings.py +++ b/purldb/settings.py @@ -53,11 +53,11 @@ INSTALLED_APPS = ( # Local apps # Must come before Third-party apps for proper templates override + 'clearcode', + 'clearindex', + 'discovery', 'matchcode', 'packagedb', - 'discovery', - 'clearindex', - 'clearcode', # Django built-in "django.contrib.auth", 'django.contrib.contenttypes', diff --git a/purldb/urls.py b/purldb/urls.py index 83b3db2b..b6260007 100644 --- a/purldb/urls.py +++ b/purldb/urls.py @@ -10,6 +10,7 @@ from django.conf.urls import include from django.urls import re_path +from clearcode.api import CDitemViewSet from packagedb.api import PackageViewSet from packagedb.api import ResourceViewSet from rest_framework import routers @@ -27,6 +28,7 @@ api_router.register(r'approximate_directory_structure_index', ApproximateDirectoryStructureIndexViewSet) api_router.register(r'exact_file_index', ExactFileIndexViewSet) api_router.register(r'exact_package_archive_index', ExactPackageArchiveIndexViewSet) +api_router.register(r'cditems', CDitemViewSet, 'cditems') urlpatterns = [ re_path(r'^api/', include((api_router.urls, 'api'))), diff --git a/setup.cfg b/setup.cfg index 0bab5b55..e07c3f2d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,7 @@ keywords = scancode purl purldb + clearcode [options] @@ -36,7 +37,6 @@ zip_safe = false install_requires = arrow==1.2.3 bitarray == 2.6.0 - clearcode-toolkit==0.0.3 debian-inspector==31.0.0 commoncode == 31.0.0 Django == 4.1.2 @@ -47,7 +47,6 @@ install_requires = jawa==2.2.0 minecode == 2.0.0 natsort>=8.2.0 - packagedb == 2.0.0 packageurl-python>=0.10.4 psycopg2-binary==2.9.3 psycopg2 == 2.9.3 @@ -78,5 +77,9 @@ docs= doc8>=0.8.1 [options.entry_points] +console_scripts = + clearsync = clearcode.sync:cli + clearload = clearcode.load:cli + scancode_post_scan = match = matchcode.plugin_match:Match From 7297acd3e2465008fc36032b2590206161c93195 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Wed, 14 Dec 2022 21:30:24 +0000 Subject: [PATCH 20/38] Update README.rst with installation instructions Signed-off-by: Jono Yang --- README.rst | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 168b35f0..406c62b2 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -The purldb +The purldb ================================ This repo consiste of two main tools: @@ -15,6 +15,74 @@ resolution, as a reference knowledge base for all packag data, as a reference for vulnerable range resolution and more. +Installation +------------ +Requirements +############ +* Debian-based Linux distribution +* Python 3.8 or later +* Postgres 13 +* git +* scancode-toolkit runtime dependencies (https://scancode-toolkit.readthedocs.io/en/stable/getting-started/install.html#install-prerequisites) + +Once the prerequisites have been installed, set up PurlDB with the following commands: +:: + + git clone https://github.com/nexb/purldb + cd purldb + ./configure --dev + make postgres + make envfile + +Once PurlDB and the database has been set up, run tests to ensure functionality: +:: + + make test + + +Post-Installation +----------------- +If you have an empty PackageDB without Package and Package Resource information, ClearCode Toolkit should be run for a while so it can populate the PackageDB with Package and Package Resource information from clearlydefined. +:: + make clearsync + +After some ClearlyDefined harvests and definitions have been obtained, run ``clearindex`` to create Packages and Resources from the harvests and definitions. +:: + make clearindex + +The Package and Package Resource information will be used to create the matching indices. +Once the PackageDB has been populated, run the following command to create the matching indices from the collected Package data: +:: + make index_packages + + +Usage +----- +Start the PurlDB server by running: +:: + make run + +You can send a ScanCode JSON scan for matching at the api/match_request/ endpoint using the HTML view or API. + +There are currently four types of matching that MatchCode provides: + +* Exact Package archive matching + + * Check the SHA1 values of archives from a scan to determine if they are known Packages + +* Exact Package file matching + + * Check the SHA1 values of files from a scan to see what Packages also has that file + +* Approximate Directory structure matching + + * Check to see if a directory and the files under it is from a known Package using the name of the files + +* Approximate Directory content matching + + * Check to see if a directory and the files under it is from a known Package using the SHA1 values of the files + + License ^^^^^^^^^^ @@ -32,6 +100,6 @@ See https://www.apache.org/licenses/LICENSE-2.0 for the license text. See https://creativecommons.org/licenses/by-sa/4.0/legalcode for the license text. -See https://github.com/nexB/purldb for support or download. +See https://github.com/nexB/purldb for support or download. See https://aboutcode.org for more information about nexB OSS projects. From 29304f2abca5e474acf59621c9f85831ba32f3d5 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Thu, 15 Dec 2022 19:44:07 +0000 Subject: [PATCH 21/38] Create clearsync and clearload management commands * Remove some redundant code Signed-off-by: Jono Yang --- clearcode/load.py | 33 +++-- clearcode/management/commands/clearload.py | 41 ++++++ clearcode/management/commands/clearsync.py | 108 +++++++++++++++ clearcode/sync.py | 129 +++++++++++------- clearindex/harvest.py | 6 - .../management/commands/run_clearindex.py | 21 +-- clearindex/utils.py | 35 ----- matchcode/management/commands/__init__.py | 37 ----- .../management/commands/index_packages.py | 2 +- matchcode/models.py | 6 +- matchcode/utils.py | 17 --- setup.cfg | 4 - 12 files changed, 259 insertions(+), 180 deletions(-) create mode 100644 clearcode/management/commands/clearload.py create mode 100644 clearcode/management/commands/clearsync.py diff --git a/clearcode/load.py b/clearcode/load.py index 8dff398b..a2889d63 100644 --- a/clearcode/load.py +++ b/clearcode/load.py @@ -81,6 +81,23 @@ def walk_and_load_from_filesystem(input_dir, cd_root_dir): continue +def load(input_dir=None, cd_root_dir=None, *arg, **kwargs): + """ + Handle ClearlyDefined gzipped JSON scans by walking a clearsync directory structure, + creating CDItem objects and loading them into a PostgreSQL database. + """ + if not input_dir: + sys.exit('Please specify an input directory using the `--input-dir` option.') + if not cd_root_dir: + sys.exit('Please specify the cd-root-directory using the --cd-root-dir option.') + + # get proper DB setup + + walk_and_load_from_filesystem(input_dir, cd_root_dir) + print(' ', end='\r') + print("Loading complete") + + @click.command() @click.option('--input-dir', @@ -98,16 +115,12 @@ def cli(input_dir=None, cd_root_dir=None, *arg, **kwargs): Handle ClearlyDefined gzipped JSON scans by walking a clearsync directory structure, creating CDItem objects and loading them into a PostgreSQL database. """ - if not input_dir: - sys.exit('Please specify an input directory using the `--input-dir` option.') - if not cd_root_dir: - sys.exit('Please specify the cd-root-directory using the --cd-root-dir option.') - - # get proper DB setup - - walk_and_load_from_filesystem(input_dir, cd_root_dir) - print(' ', end='\r') - print("Loading complete") + load( + input_dir=input_dir, + cd_root_dir=cd_root_dir, + *arg, + **kwargs + ) if __name__ == '__main__': diff --git a/clearcode/management/commands/clearload.py b/clearcode/management/commands/clearload.py new file mode 100644 index 00000000..8b6805f9 --- /dev/null +++ b/clearcode/management/commands/clearload.py @@ -0,0 +1,41 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +from clearcode.load import load +from discovery.management.commands import VerboseCommand + + +class Command(VerboseCommand): + help = """ + Handle ClearlyDefined gzipped JSON scans by walking a clearsync directory structure, + creating CDItem objects and loading them into a PostgreSQL database. + """ + + def add_arguments(self, parser): + parser.add_argument( + '--input-dir', + dest='input_dir', + default=None, + type=str, + help='Load content from this input directory that contains a tree of gzip-compressed JSON CD files') + parser.add_argument( + '--cd-root-dir', + dest='cd_root_dir', + default=None, + type=str, + help='Specify root directory that contains a tree of gzip-compressed JSON CD files') + + def handle(self, *args, **options): + input_dir = options.get('input_dir') + cd_root_dir = options.get('cd_root_dir') + + load( + input_dir=input_dir, + cd_root_dir=cd_root_dir + ) diff --git a/clearcode/management/commands/clearsync.py b/clearcode/management/commands/clearsync.py new file mode 100644 index 00000000..9efc1d33 --- /dev/null +++ b/clearcode/management/commands/clearsync.py @@ -0,0 +1,108 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +from clearcode.sync import sync +from discovery.management.commands import VerboseCommand + + +class Command(VerboseCommand): + help = """ + Fetch the latest definitions and harvests from ClearlyDefined and save these + as gzipped JSON either as as files in output-dir or in a PostgreSQL + database. Loop forever after waiting some seconds between each cycles. + """ + + def add_arguments(self, parser): + parser.add_argument( + '--output-dir', + dest='output_dir', + default=None, + type=str, + help='Save fetched content as compressed gzipped files to this output directory.') + parser.add_argument( + '--save-to-db', + dest='save_to_db', + default=True, + action='store_false', + help='Save fetched content as compressed gzipped blobs in the configured database.') + parser.add_argument( + '--unsorted', + dest='unsorted', + default=False, + action='store_true', + help='Fetch data without any sorting. The default is to fetch data sorting by latest updated first.') + parser.add_argument( + '--base-api-url', + dest='base_api_url', + default='https://api.clearlydefined.io', + help='ClearlyDefined base API URL.') + parser.add_argument( + '--wait', + dest='wait', + default=60, + type=int, + help='Set the number of seconds to wait for new or updated definitions ' + 'between two loops.') + parser.add_argument( + '-n', + '--processes', + dest='processes', + default=1, + type=int, + help='Set the number of parallel processes to use. ' + 'Disable parallel processing if 0.') + parser.add_argument( + '--max-def', + dest='max_def', + default=0, + type=int, + help='Set the maximum number of definitions to fetch.') + parser.add_argument( + '--only-definitions', + dest='only_definitions', + default=False, + action='store_true', + help='Only fetch definitions and no other data item.') + parser.add_argument( + '--log-file', + dest='log_file', + default=None, + help='Path to a file where to log fetched paths, one per line. ' + 'Log entries will be appended to this file if it exists.') + parser.add_argument( + '--verbose', + dest='verbose', + default=False, + action='store_true', + help='Display more verbose progress messages.') + + def handle(self, *args, **options): + output_dir = options.get('output_dir') + save_to_db = options.get('save_to_db', True) + base_api_url = options.get('base_api_url', 'https://api.clearlydefined.io') + wait = options.get('wait', 60) + processes = options.get('processes', 1) + unsorted = options.get('unsorted', False) + log_file = options.get('log_file') + max_def = options.get('max_def', 0) + only_definitions = options.get('only_definitions', False) + verbose = options.get('verbose', False) + + sync( + output_dir=output_dir, + save_to_db=save_to_db, + base_api_url=base_api_url, + wait=wait, + processes=processes, + unsorted=unsorted, + log_file=log_file, + max_def=max_def, + only_definitions=only_definitions, + verbose=verbose + ) diff --git a/clearcode/sync.py b/clearcode/sync.py index 8b7f3d8e..6b39d526 100644 --- a/clearcode/sync.py +++ b/clearcode/sync.py @@ -393,57 +393,7 @@ def copy(self): return cache -@click.command() - -@click.option('--output-dir', - type=click.Path(), metavar='DIR', - help='Save fetched content as compressed gzipped files to this output directory.') - -@click.option('--save-to-db', - is_flag=True, - help='Save fetched content as compressed gzipped blobs in the configured database.') - -@click.option('--unsorted', - is_flag=True, - help='Fetch data without any sorting. The default is to fetch data sorting by latest updated first.') - -@click.option('--base-api-url', - type=str, - default='https://api.clearlydefined.io', show_default=True, - help='ClearlyDefined base API URL.') - -@click.option('--wait', - type=int, metavar='INT', - default=60, show_default=True, - help='Set the number of seconds to wait for new or updated definitions ' - 'between two loops.') - -@click.option('-n', '--processes', - type=int, metavar='INT', - default=1, show_default=True, - help='Set the number of parallel processes to use. ' - 'Disable parallel processing if 0.') - -@click.option('--max-def', - type=int, metavar='INT', - default=0, - help='Set the maximum number of definitions to fetch.') - -@click.option('--only-definitions', - is_flag=True, - help='Only fetch definitions and no other data item.') - -@click.option('--log-file', - type=click.Path(), default=None, - help='Path to a file where to log fetched paths, one per line. ' - 'Log entries will be appended to this file if it exists.') - -@click.option('--verbose', - is_flag=True, - help='Display more verbose progress messages.') - -@click.help_option('-h', '--help') -def cli(output_dir=None, save_to_db=False, +def sync(output_dir=None, save_to_db=False, base_api_url='https://api.clearlydefined.io', wait=60, processes=1, unsorted=False, log_file=None, max_def=0, only_definitions=False, session=session, @@ -573,5 +523,82 @@ def cli(output_dir=None, save_to_db=False, 'in:', int(total_duration), 'sec.') +@click.command() + +@click.option('--output-dir', + type=click.Path(), metavar='DIR', + help='Save fetched content as compressed gzipped files to this output directory.') + +@click.option('--save-to-db', + is_flag=True, + help='Save fetched content as compressed gzipped blobs in the configured database.') + +@click.option('--unsorted', + is_flag=True, + help='Fetch data without any sorting. The default is to fetch data sorting by latest updated first.') + +@click.option('--base-api-url', + type=str, + default='https://api.clearlydefined.io', show_default=True, + help='ClearlyDefined base API URL.') + +@click.option('--wait', + type=int, metavar='INT', + default=60, show_default=True, + help='Set the number of seconds to wait for new or updated definitions ' + 'between two loops.') + +@click.option('-n', '--processes', + type=int, metavar='INT', + default=1, show_default=True, + help='Set the number of parallel processes to use. ' + 'Disable parallel processing if 0.') + +@click.option('--max-def', + type=int, metavar='INT', + default=0, + help='Set the maximum number of definitions to fetch.') + +@click.option('--only-definitions', + is_flag=True, + help='Only fetch definitions and no other data item.') + +@click.option('--log-file', + type=click.Path(), default=None, + help='Path to a file where to log fetched paths, one per line. ' + 'Log entries will be appended to this file if it exists.') + +@click.option('--verbose', + is_flag=True, + help='Display more verbose progress messages.') + +@click.help_option('-h', '--help') +def cli(output_dir=None, save_to_db=False, + base_api_url='https://api.clearlydefined.io', + wait=60, processes=1, unsorted=False, + log_file=None, max_def=0, only_definitions=False, session=session, + verbose=False, *arg, **kwargs): + """ + Fetch the latest definitions and harvests from ClearlyDefined and save these + as gzipped JSON either as as files in output-dir or in a PostgreSQL + database. Loop forever after waiting some seconds between each cycles. + """ + sync( + output_dir=output_dir, + save_to_db=save_to_db, + base_api_url=base_api_url, + wait=wait, + processes=processes, + unsorted=unsorted, + log_file=log_file, + max_def=max_def, + only_definitions=only_definitions, + session=session, + verbose=verbose, + *arg, + **kwargs, + ) + + if __name__ == '__main__': cli() diff --git a/clearindex/harvest.py b/clearindex/harvest.py index 47aa2dce..eda9a664 100644 --- a/clearindex/harvest.py +++ b/clearindex/harvest.py @@ -7,18 +7,12 @@ # See https://aboutcode.org for more information about nexB OSS projects. # -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals - import logging import sys -from django.db import IntegrityError from django.db import transaction from django.utils import timezone -from clearcode.models import CDitem from packagedb.models import Package from packagedb.models import Resource diff --git a/clearindex/management/commands/run_clearindex.py b/clearindex/management/commands/run_clearindex.py index 5e13b33f..a04118ce 100644 --- a/clearindex/management/commands/run_clearindex.py +++ b/clearindex/management/commands/run_clearindex.py @@ -7,10 +7,6 @@ # See https://aboutcode.org for more information about nexB OSS projects. # -from __future__ import absolute_import -from __future__ import unicode_literals -from __future__ import print_function - import logging import signal import sys @@ -21,13 +17,6 @@ from django.db.utils import OperationalError from django.utils import timezone -from clearcode.models import CDitem -from clearindex.utils import get_error_message -from clearindex.utils import VerboseCommand -from discovery.management.commands.run_map import merge_packages -from discovery.utils import stringify_null_purl_fields - -from packagedb.models import Package from packagedcode import licensing from packagedcode import maven from packagedcode import npm @@ -36,10 +25,14 @@ from packagedcode import rubygems from packagedcode.models import Package as ScannedPackage - +from clearcode.models import CDitem from clearindex import harvest -from clearindex.utils import get_error_message -from clearindex.utils import VerboseCommand +from discovery.management.commands import get_error_message +from discovery.management.commands import VerboseCommand +from discovery.management.commands.run_map import merge_packages +from discovery.utils import stringify_null_purl_fields +from packagedb.models import Package + TRACE = False diff --git a/clearindex/utils.py b/clearindex/utils.py index c21cbea1..ef6b28c4 100644 --- a/clearindex/utils.py +++ b/clearindex/utils.py @@ -20,41 +20,6 @@ from discovery.utils_test import JsonBasedTesting -class VerboseCommand(BaseCommand): - """ - Base verbosity-aware Command. - Command modules should define logging and subclasses should call - logger.setLevel(self.get_verbosity(**options)) - in their handle() method. - """ - - def get_verbosity(self, **options): - verbosity = int(options.get('verbosity', 1)) - levels = {1: logging.INFO, 2: logging.ERROR, 3: logging.DEBUG} - return levels.get(verbosity, logging.CRITICAL) - - MUST_STOP = False - - @classmethod - def stop_handler(cls, *args, **kwargs): - """ - Signal handler use to support a graceful exit when flag is to True. - Subclasses must create this signal to use this: - signal.signal(signal.SIGTERM, Command.stop_handler) - """ - cls.MUST_STOP = True - - -def get_error_message(e): - """ - Return an error message with a traceback given an exception. - """ - tb = traceback.format_exc() - msg = e.__class__.__name__ + ' ' + repr(e) - msg += '\n' + tb - return msg - - """ The conventions used for the tests are: - for tests that require files these are stored in the testfiles directory diff --git a/matchcode/management/commands/__init__.py b/matchcode/management/commands/__init__.py index d0fa2322..e69de29b 100644 --- a/matchcode/management/commands/__init__.py +++ b/matchcode/management/commands/__init__.py @@ -1,37 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -import logging - -from django.core.management.base import BaseCommand - - -class VerboseCommand(BaseCommand): - """ - Base verbosity-aware Command. - Command modules should define logging and subclasses should call - logger.setLevel(self.get_verbosity(**options)) - in their handle() method. - """ - - def get_verbosity(self, **options): - verbosity = int(options.get('verbosity', 1)) - levels = {1: logging.INFO, 2: logging.ERROR, 3: logging.DEBUG} - return levels.get(verbosity, logging.CRITICAL) - - MUST_STOP = False - - @classmethod - def stop_handler(cls, *args, **kwargs): - """ - Signal handler use to support a graceful exit when flag is to True. - Subclasses must create this signal to use this: - signal.signal(signal.SIGTERM, Command.stop_handler) - """ - cls.MUST_STOP = True diff --git a/matchcode/management/commands/index_packages.py b/matchcode/management/commands/index_packages.py index e17a1d8e..e246d006 100644 --- a/matchcode/management/commands/index_packages.py +++ b/matchcode/management/commands/index_packages.py @@ -14,10 +14,10 @@ from django.db import transaction +from discovery.management.commands import VerboseCommand from matchcode.indexing import index_package_archives from matchcode.indexing import index_package_directories from matchcode.indexing import index_package_file -from matchcode.management.commands import VerboseCommand from packagedb.models import Package from packagedb.models import Resource diff --git a/matchcode/models.py b/matchcode/models.py index cbf8de30..e5c51559 100644 --- a/matchcode/models.py +++ b/matchcode/models.py @@ -13,20 +13,16 @@ import logging import sys -from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.forms.models import model_to_dict -from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from discovery.management.commands import get_error_message from matchcode.fingerprinting import create_halohash_chunks from matchcode.fingerprinting import split_fingerprint from matchcode.halohash import byte_hamming_distance -from matchcode.utils import get_error_message from matchcode.utils import hexstring_to_binarray - from packagedb.models import Package -from packagedb.models import Resource TRACE = False diff --git a/matchcode/utils.py b/matchcode/utils.py index 09a603e8..9a04889f 100644 --- a/matchcode/utils.py +++ b/matchcode/utils.py @@ -156,23 +156,6 @@ def hexstring_to_binarray(hex_string): return bytearray(binascii.unhexlify(hex_string)) -def get_error_message(e): - """ - Return an error message with a traceback given an exception. - """ - tb = traceback.format_exc() - msg = e.__class__.__name__ + ' ' + repr(e) - msg += '\n' + tb - return msg - - -def get_settings(var_name): - """ - Return the settings value from the environment or Django settings. - """ - return getenv(var_name) or getattr(settings, var_name, None) or '' - - def path_suffixes(path): """ Yield all the suffixes of `path`, starting from the longest (e.g. more segments). diff --git a/setup.cfg b/setup.cfg index e07c3f2d..eacea8c2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -77,9 +77,5 @@ docs= doc8>=0.8.1 [options.entry_points] -console_scripts = - clearsync = clearcode.sync:cli - clearload = clearcode.load:cli - scancode_post_scan = match = matchcode.plugin_match:Match From 0dba9cdb66f6a4883e39e2893ad6b60fc113c3d8 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Thu, 15 Dec 2022 20:22:50 +0000 Subject: [PATCH 22/38] Update Github CI tests * Remove individual test setups in favor of a single one Signed-off-by: Jono Yang --- .github/workflows/clearcode-tests.yml | 54 ------------------ .github/workflows/docs-ci.yml | 6 +- .github/workflows/minecode-tests.yml | 55 ------------------- .github/workflows/packagedb-tests.yml | 55 ------------------- .../{matchcode-tests.yml => purldb-tests.yml} | 6 +- 5 files changed, 6 insertions(+), 170 deletions(-) delete mode 100644 .github/workflows/clearcode-tests.yml delete mode 100644 .github/workflows/minecode-tests.yml delete mode 100644 .github/workflows/packagedb-tests.yml rename .github/workflows/{matchcode-tests.yml => purldb-tests.yml} (92%) diff --git a/.github/workflows/clearcode-tests.yml b/.github/workflows/clearcode-tests.yml deleted file mode 100644 index 304a4376..00000000 --- a/.github/workflows/clearcode-tests.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Clearcode Tests CI - -on: [push, pull_request] - -env: - POSTGRES_DB: packagedb - POSTGRES_USER: packagedb - POSTGRES_PASSWORD: packagedb - POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8 - -jobs: - build: - runs-on: ubuntu-20.04 - - services: - postgres: - image: postgres:13 - env: - POSTGRES_DB: ${{ env.POSTGRES_DB }} - POSTGRES_USER: ${{ env.POSTGRES_USER }} - POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }} - POSTGRES_INITDB_ARGS: ${{ env.POSTGRES_INITDB_ARGS }} - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - strategy: - max-parallel: 4 - matrix: - python-version: ["3.8", "3.9", "3.10"] - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - working-directory: ./clearcode-toolkit - run: | - ./configure - - - name: Run tests - working-directory: ./clearcode-toolkit - run: | - source bin/activate - python manage.py test clearcode --verbosity=2 diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml index cc0cbb57..2b10c392 100644 --- a/.github/workflows/docs-ci.yml +++ b/.github/workflows/docs-ci.yml @@ -24,17 +24,17 @@ jobs: run: chmod +x ./docs/scripts/doc8_style_check.sh - name: Install Dependencies - working-directory: ./minecode + working-directory: . run: ./configure --docs - name: Check Sphinx Documentation build minimally working-directory: ./docs run: | - source ../minecode/venv/bin/activate + source ../venv/bin/activate sphinx-build -E -W source build - name: Check for documentation style errors working-directory: ./docs run: | - source ../minecode/venv/bin/activate + source ../venv/bin/activate ./scripts/doc8_style_check.sh diff --git a/.github/workflows/minecode-tests.yml b/.github/workflows/minecode-tests.yml deleted file mode 100644 index e98b3575..00000000 --- a/.github/workflows/minecode-tests.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Minecode Tests CI - -on: [push, pull_request] - -env: - POSTGRES_DB: packagedb - POSTGRES_USER: packagedb - POSTGRES_PASSWORD: packagedb - POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8 - -jobs: - build: - runs-on: ubuntu-20.04 - - services: - postgres: - image: postgres:13 - env: - POSTGRES_DB: ${{ env.POSTGRES_DB }} - POSTGRES_USER: ${{ env.POSTGRES_USER }} - POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }} - POSTGRES_INITDB_ARGS: ${{ env.POSTGRES_INITDB_ARGS }} - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - strategy: - max-parallel: 4 - matrix: - python-version: ["3.8", "3.9", "3.10"] - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - working-directory: ./minecode - run: | - ./configure --dev - - - name: Run tests - working-directory: ./minecode - run: | - make envfile - source venv/bin/activate - python manage.py test diff --git a/.github/workflows/packagedb-tests.yml b/.github/workflows/packagedb-tests.yml deleted file mode 100644 index d7734bd8..00000000 --- a/.github/workflows/packagedb-tests.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: PackageDB Tests CI - -on: [push, pull_request] - -env: - POSTGRES_DB: packagedb - POSTGRES_USER: packagedb - POSTGRES_PASSWORD: packagedb - POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8 - -jobs: - build: - runs-on: ubuntu-20.04 - - services: - postgres: - image: postgres:13 - env: - POSTGRES_DB: ${{ env.POSTGRES_DB }} - POSTGRES_USER: ${{ env.POSTGRES_USER }} - POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }} - POSTGRES_INITDB_ARGS: ${{ env.POSTGRES_INITDB_ARGS }} - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - strategy: - max-parallel: 4 - matrix: - python-version: ["3.8", "3.9", "3.10"] - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - working-directory: ./packagedb - run: | - ./configure --dev - - - name: Run tests - working-directory: ./packagedb - run: | - make envfile - source venv/bin/activate - python manage.py test diff --git a/.github/workflows/matchcode-tests.yml b/.github/workflows/purldb-tests.yml similarity index 92% rename from .github/workflows/matchcode-tests.yml rename to .github/workflows/purldb-tests.yml index 49277813..3601ccc6 100644 --- a/.github/workflows/matchcode-tests.yml +++ b/.github/workflows/purldb-tests.yml @@ -1,4 +1,4 @@ -name: Matchcode Tests CI +name: PurlDB Tests CI on: [push, pull_request] @@ -43,12 +43,12 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies - working-directory: ./matchcode + working-directory: . run: | make dev - name: Run tests - working-directory: ./matchcode + working-directory: . run: | make envfile make test From 0d6d94bffa02f75d84f3662d1fec79ab43ff2678 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Fri, 16 Dec 2022 00:37:29 +0000 Subject: [PATCH 23/38] Update README.rst * Fix clearsync Makefile command Signed-off-by: Jono Yang --- Makefile | 2 +- README.rst | 39 ++++++++++++++-------- clearcode/management/commands/clearsync.py | 23 ++++++------- setup.cfg | 27 ++++++++------- 4 files changed, 50 insertions(+), 41 deletions(-) diff --git a/Makefile b/Makefile index c074eb20..5de3011a 100644 --- a/Makefile +++ b/Makefile @@ -98,7 +98,7 @@ shell: ${MANAGE} shell clearsync: - ${ACTIVATE} clearsync --save-to-db --verbose -n 3 + ${MANAGE} clearsync --save-to-db --verbose -n 3 clearindex: ${MANAGE} run_clearindex diff --git a/README.rst b/README.rst index 406c62b2..ce211f23 100644 --- a/README.rst +++ b/README.rst @@ -1,18 +1,21 @@ The purldb -================================ -This repo consiste of two main tools: +========== +This repo consists of four main tools: -- MineCode that contains utilities to mine package repositories - PackageDB that is the reference model (based on ScanCode toolkit) that contains package data with purl (Package URLs) being a first class citizen. +- MineCode that contains utilities to mine package repositories +- MatchCode that contains utilities to index package metadata and resources for + matching +- ClearCode that contains utilities to mine Clearlydefined for package data -These are designed to be used first for reference such that one can -query by purl and validate purl existence. +These are designed to be used first for reference such that one can query for +packages by purl and validate purl existence. -In the future, these will be used as reference for dependency -resolution, as a reference knowledge base for all packag data, -as a reference for vulnerable range resolution and more. +In the future, the collected packages will be used as reference for dependency +resolution, as a reference knowledge base for all package data, as a reference +for vulnerable range resolution and more. Installation @@ -30,7 +33,7 @@ Once the prerequisites have been installed, set up PurlDB with the following com git clone https://github.com/nexb/purldb cd purldb - ./configure --dev + make dev make postgres make envfile @@ -42,17 +45,26 @@ Once PurlDB and the database has been set up, run tests to ensure functionality: Post-Installation ----------------- -If you have an empty PackageDB without Package and Package Resource information, ClearCode Toolkit should be run for a while so it can populate the PackageDB with Package and Package Resource information from clearlydefined. +If you have an empty PackageDB without Package and Package Resource information, +ClearCode should be run for a while so it can populate the PackageDB +with Package and Package Resource information from clearlydefined. :: + make clearsync -After some ClearlyDefined harvests and definitions have been obtained, run ``clearindex`` to create Packages and Resources from the harvests and definitions. +After some ClearlyDefined harvests and definitions have been obtained, run +``clearindex`` to create Packages and Resources from the harvests and +definitions. :: + make clearindex The Package and Package Resource information will be used to create the matching indices. -Once the PackageDB has been populated, run the following command to create the matching indices from the collected Package data: + +Once the PackageDB has been populated, run the following command to create the +matching indices from the collected Package data: :: + make index_packages @@ -60,6 +72,7 @@ Usage ----- Start the PurlDB server by running: :: + make run You can send a ScanCode JSON scan for matching at the api/match_request/ endpoint using the HTML view or API. @@ -84,7 +97,7 @@ There are currently four types of matching that MatchCode provides: License -^^^^^^^^^^ +------- Copyright (c) nexB Inc. and others. All rights reserved. diff --git a/clearcode/management/commands/clearsync.py b/clearcode/management/commands/clearsync.py index 9efc1d33..b7369297 100644 --- a/clearcode/management/commands/clearsync.py +++ b/clearcode/management/commands/clearsync.py @@ -28,13 +28,11 @@ def add_arguments(self, parser): parser.add_argument( '--save-to-db', dest='save_to_db', - default=True, - action='store_false', + action='store_true', help='Save fetched content as compressed gzipped blobs in the configured database.') parser.add_argument( '--unsorted', dest='unsorted', - default=False, action='store_true', help='Fetch data without any sorting. The default is to fetch data sorting by latest updated first.') parser.add_argument( @@ -66,33 +64,32 @@ def add_arguments(self, parser): parser.add_argument( '--only-definitions', dest='only_definitions', - default=False, action='store_true', help='Only fetch definitions and no other data item.') parser.add_argument( '--log-file', dest='log_file', default=None, + type=str, help='Path to a file where to log fetched paths, one per line. ' 'Log entries will be appended to this file if it exists.') parser.add_argument( '--verbose', dest='verbose', - default=False, action='store_true', help='Display more verbose progress messages.') def handle(self, *args, **options): output_dir = options.get('output_dir') - save_to_db = options.get('save_to_db', True) - base_api_url = options.get('base_api_url', 'https://api.clearlydefined.io') - wait = options.get('wait', 60) - processes = options.get('processes', 1) - unsorted = options.get('unsorted', False) + save_to_db = options.get('save_to_db') + base_api_url = options.get('base_api_url') + wait = options.get('wait') + processes = options.get('processes') + unsorted = options.get('unsorted') log_file = options.get('log_file') - max_def = options.get('max_def', 0) - only_definitions = options.get('only_definitions', False) - verbose = options.get('verbose', False) + max_def = options.get('max_def') + only_definitions = options.get('only_definitions') + verbose = options.get('verbose') sync( output_dir=output_dir, diff --git a/setup.cfg b/setup.cfg index eacea8c2..2d2fcfbd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,7 +10,7 @@ author_email = info@aboutcode.org license = Apache-2.0 AND CC-BY-SA-4.0 # description must be on ONE line https://github.com/pypa/setuptools/issues/1390 -description = MatchCode +description = A Django-based utility to collect Package data and Resources for collection and matching. long_description = file:README.rst long_description_content_type = text/x-rst url = https://github.com/nexB/purldb @@ -35,26 +35,25 @@ packages = find: include_package_data = true zip_safe = false install_requires = - arrow==1.2.3 + arrow == 1.2.3 bitarray == 2.6.0 - debian-inspector==31.0.0 + debian-inspector == 31.0.0 commoncode == 31.0.0 Django == 4.1.2 - django-environ==0.9.0 + django-environ == 0.9.0 djangorestframework == 3.14.0 django-filter == 22.1 - ftputil==5.0.4 - jawa==2.2.0 - minecode == 2.0.0 - natsort>=8.2.0 - packageurl-python>=0.10.4 - psycopg2-binary==2.9.3 + ftputil == 5.0.4 + jawa == 2.2.0 + natsort == 8.2.0 + packageurl-python == 0.10.4 + psycopg2-binary == 2.9.3 psycopg2 == 2.9.3 - PyGithub==1.56 - reppy2==0.3.6 - rubymarshal==1.0.3 + PyGithub == 1.56 + reppy2 == 0.3.6 + rubymarshal == 1.0.3 scancode-toolkit == 31.2.2 - urlpy==0.5 + urlpy == 0.5 setup_requires = setuptools_scm[toml] >= 4 python_requires = >=3.8.* From 8223212d3fd3607c735be18d6c552679c2a21bc9 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Fri, 16 Dec 2022 00:54:40 +0000 Subject: [PATCH 24/38] Remove rst files from subdirectories Signed-off-by: Jono Yang --- AUTHORS.rst | 6 +++- CHANGELOG.rst | 4 +-- discovery/AUTHORS.rst | 7 ---- discovery/CHANGELOG.rst | 10 ------ discovery/NOTICE | 12 ------- discovery/README.rst | 12 ------- matchcode/AUTHORS.rst | 3 -- matchcode/CHANGELOG.rst | 53 ------------------------------ matchcode/README.rst | 71 ----------------------------------------- 9 files changed, 7 insertions(+), 171 deletions(-) delete mode 100644 discovery/AUTHORS.rst delete mode 100644 discovery/CHANGELOG.rst delete mode 100644 discovery/NOTICE delete mode 100644 discovery/README.rst delete mode 100644 matchcode/AUTHORS.rst delete mode 100644 matchcode/CHANGELOG.rst delete mode 100644 matchcode/README.rst diff --git a/AUTHORS.rst b/AUTHORS.rst index 51a19cc8..c3f8bfc8 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,3 +1,7 @@ The following organizations or individuals have contributed to this repo: -- +- nexB Inc. +- Jono Yang +- Philippe Ombredanne +- Li Ha +- Steven Esser \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fc2b6e32..6c3e42ee 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,7 +2,7 @@ Changelog ========= -v0.0.0 +v2.0.0 ------ -*xxxx-xx-xx* -- Initial release. +*2022-11-11* -- Initial release. diff --git a/discovery/AUTHORS.rst b/discovery/AUTHORS.rst deleted file mode 100644 index c3f8bfc8..00000000 --- a/discovery/AUTHORS.rst +++ /dev/null @@ -1,7 +0,0 @@ -The following organizations or individuals have contributed to this repo: - -- nexB Inc. -- Jono Yang -- Philippe Ombredanne -- Li Ha -- Steven Esser \ No newline at end of file diff --git a/discovery/CHANGELOG.rst b/discovery/CHANGELOG.rst deleted file mode 100644 index 7c378e5d..00000000 --- a/discovery/CHANGELOG.rst +++ /dev/null @@ -1,10 +0,0 @@ - -Release notes -============= - - - -Version v1.0.0 ----------------- - -Initial release \ No newline at end of file diff --git a/discovery/NOTICE b/discovery/NOTICE deleted file mode 100644 index dab28052..00000000 --- a/discovery/NOTICE +++ /dev/null @@ -1,12 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 AND CC-BY-SA-4.0 -# purldb software is licensed under the Apache License version 2.0. -# purldb data is licensed collectively under CC-BY-SA-4.0. -# See https://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://creativecommons.org/licenses/by-sa/4.0/legalcode for the license text. -# -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# diff --git a/discovery/README.rst b/discovery/README.rst deleted file mode 100644 index 917c93a0..00000000 --- a/discovery/README.rst +++ /dev/null @@ -1,12 +0,0 @@ -minecode -========= - -MineCode is a Django web app that visits upstream package repositories and saves the package metadata to the PackageDB. - -Local setup -=========== - -* ./configure -* make postgres -* make envfile -* make run diff --git a/matchcode/AUTHORS.rst b/matchcode/AUTHORS.rst deleted file mode 100644 index 51a19cc8..00000000 --- a/matchcode/AUTHORS.rst +++ /dev/null @@ -1,3 +0,0 @@ -The following organizations or individuals have contributed to this repo: - -- diff --git a/matchcode/CHANGELOG.rst b/matchcode/CHANGELOG.rst deleted file mode 100644 index 678111de..00000000 --- a/matchcode/CHANGELOG.rst +++ /dev/null @@ -1,53 +0,0 @@ -Changelog -========= - -next-next-next-release (2022-XX-XX) ------------------------------------ - * Bump dependency versions - * Update Django to 4.0.4 - * Update djangorestframework to 3.13.1 - * Update django-filter to 21.1 - * Update packagedb to 1.2.3 - * Update psycopg2 to 2.9.3 - * Use django.utils.translation.gettext_lazy instead of django.utils.translation.ugettext_lazy - * Use django.urls.re_path instead of django.conf.urls.url - -next-next-release (2021-XX-XX) ------------------------------- - - * Remove MatchRequest and Match models and APIs from matchcode. Matchcode will provide API - access to the different matching indices and return results for individual - fingerprint lookups instead of running the matching process on the entire - codebase of Resources at once. - * Reorganize fingerprinting, indexing, and matching functions into their own files. - * Modify matching functions and tests to work without the MatchRequest and Match models. - These functions will go into a scanpipe pipes/pipeline later. - -next-release (2021-XX-XX) -------------------------- - - * Improved approximate directory structure and approximate directory content - matching available through API - * We now return exact directory matches, if available. If no exact match is found, - the closest matches are returned. - * Package matches are placed in top-level ``packages`` field in JSON results. - Package matches are related to files using the purl of a package. - * Show stats when the command index_packages is done running - * Send match completion notifications via webhooks. MatchRequest API now - accepts webhook URLs during creation. - * Delay running matching task for 10 seconds after a MatchRequest is received. - This is to ensure that the received MatchRequest is written to the database before being used. - * The field `indexed_elements_count` has been added to `BaseDirectoryIndex`. - `indexed_elements_count` is an integer that represents the number of inputs used to create the - fingerprint. During a match, a fingerprint is compared to other fingerprints whose size is - within 5% of our fingerprints size. - * File size is now used in the creation of ApproximateDirectoryStructureIndex fingerprints. - -v0.0.1 (201X-XX-XX) -------------------- - - * Initial release - * SHA1 Package matching available through API (``api/match_request``) - * label, uuid, created_date, task_start_date, task_end_date, status, - execution_time, input_scan, and match_results for a given match request - available through API diff --git a/matchcode/README.rst b/matchcode/README.rst deleted file mode 100644 index 4f5cccd1..00000000 --- a/matchcode/README.rst +++ /dev/null @@ -1,71 +0,0 @@ -========= -MatchCode -========= -MatchCode is a server that detects Packages from ScanCode scans of a codebase. - -Installation ------------- -Requirements -############ -* Debian-based Linux distribution -* Python 3.9 or later -* Postgres 13 -* git -* scancode-toolkit runtime dependencies (https://scancode-toolkit.readthedocs.io/en/stable/getting-started/install.html#install-prerequisites) - -Once the prerequisites have been installed, set up MatchCode with the following commands: -:: - - git clone https://github.com/nexb/purldb - cd purldb/matchcode - ./configure --dev - make postgres - make envfile - -Once MatchCode and the database has been set up, run tests to ensure functionality: -:: - - make test - - -Post-Installation ------------------ -If you have an empty PackageDB without Package and Package Resource information, ClearCode Toolkit should be run for a while so it can populate the PackageDB with Package and Package Resource information from clearlydefined. -:: - make clearsync - -After some ClearlyDefined harvests and definitions have been obtained, run ``clearindex`` to create Packages and Resources from the harvests and definitions. -:: - make clearindex - -The Package and Package Resource information will be used to create the matching indices. -Once the PackageDB has been populated, run the following command to create the matching indices from the collected Package data: -:: - make index_packages - - -Usage ------ -Start the MatchCode server by running: -:: - make run - -You can send a ScanCode JSON scan for matching at the api/match_request/ endpoint using the HTML view or API. - -There are currently four types of matching that MatchCode provides: - -* Exact Package archive matching - - * Check the SHA1 values of archives from a scan to determine if they are known Packages - -* Exact Package file matching - - * Check the SHA1 values of files from a scan to see what Packages also has that file - -* Approximate Directory structure matching - - * Check to see if a directory and the files under it is from a known Package using the name of the files - -* Approximate Directory content matching - - * Check to see if a directory and the files under it is from a known Package using the SHA1 values of the files From 96b480d971f0980dfa908a002248eb5bfa7056f4 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Fri, 16 Dec 2022 01:05:41 +0000 Subject: [PATCH 25/38] Update requirements files Signed-off-by: Jono Yang --- requirements-dev.txt | 9 +++++---- requirements.txt | 18 ++++++++++++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 65b64a2b..b40431ee 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,14 +1,15 @@ aboutcode-toolkit==7.2.0 -black==22.10.0 +black==22.12.0 et-xmlfile==1.1.0 exceptiongroup==1.0.4 execnet==1.9.0 iniconfig==1.1.1 +mock==4.0.3 mypy-extensions==0.4.3 openpyxl==3.0.10 -pathspec==0.10.2 -platformdirs==2.5.4 +pathspec==0.10.3 +platformdirs==2.6.0 pytest==7.2.0 pytest-django==4.5.2 -pytest-xdist==3.0.2 +pytest-xdist==3.1.0 tomli==2.0.1 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 7e27df4d..16cc9a69 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +arrow==1.2.3 asgiref==3.5.2 attrs==22.1.0 banal==1.0.6 @@ -11,10 +12,11 @@ chardet==5.1.0 charset-normalizer==2.1.1 click==8.1.3 colorama==0.4.6 -commoncode==31.0.0 +commoncode @ https://github.com/nexB/commoncode/archive/refs/heads/48-correctly-assign-codebase-attributes.zip container-inspector==32.0.1 cryptography==38.0.4 debian-inspector==31.0.0 +Deprecated==1.2.13 Django==4.1.2 django-environ==0.9.0 django-filter==22.1 @@ -27,6 +29,7 @@ extractcode-libarchive==3.5.1.210531 fasteners==0.18 fingerprints==1.0.3 ftfy==6.1.1 +ftputil==5.0.4 future==0.18.2 gemfileparser2==0.9.3 html5lib==1.1 @@ -36,6 +39,7 @@ intbitset==3.0.1 isodate==0.6.1 jaraco.functools==3.5.2 javaproperties==0.8.1 +jawa==2.2.0 Jinja2==3.1.2 jsonstreams==0.6.0 license-expression==30.0.0 @@ -44,13 +48,12 @@ MarkupSafe==2.1.1 more-itertools==9.0.0 natsort==8.2.0 normality==2.4.0 -packagedb==2.0.0 packageurl-python==0.10.4 packaging==21.3 parameter-expansion-patched==0.3.1 pdfminer.six==20221105 pefile==2022.5.30 -pip==21.1.2 +pip==22.2.2 pip-requirements-parser==31.2.0 pkginfo2==30.0.0 pluggy==1.0.0 @@ -61,14 +64,20 @@ psycopg2-binary==2.9.3 publicsuffix2==2.20191221 pyahocorasick==2.0.0b1 pycparser==2.21 +PyGithub==1.56 pygmars==0.8.0 Pygments==2.13.0 +PyJWT==2.6.0 pymaven-patch==0.3.0 +PyNaCl==1.5.0 pyparsing==3.0.9 +python-dateutil==2.8.2 pytz==2022.6 PyYAML==6.0 rdflib==6.2.0 +reppy2==0.3.6 requests==2.28.1 +rubymarshal==1.0.3 saneyaml==0.5.2 scancode-toolkit @ https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip setuptools==57.0.0 @@ -84,6 +93,7 @@ urllib3==1.26.13 urlpy==0.5 wcwidth==0.2.5 webencodings==0.5.1 -wheel==0.36.2 +wheel==0.37.1 +wrapt==1.14.1 xmltodict==0.13.0 zipp==3.11.0 From 3d92cc115010a944e4c2abe79d68e09b5c825e2d Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Fri, 16 Dec 2022 01:52:35 +0000 Subject: [PATCH 26/38] Update README.rst * Add descriptions for the API endpoints * Add instructions for visiting and mapping * Add Makefile commands for visiting and mapping Signed-off-by: Jono Yang --- Makefile | 9 +++++++++ README.rst | 57 ++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index 5de3011a..b9db1e79 100644 --- a/Makefile +++ b/Makefile @@ -90,6 +90,15 @@ postgres: run: ${MANAGE} runserver 8001 --insecure +seed: + ${MANAGE} seed + +run_visit: seed + ${MANAGE} run_visit + +run_map: + ${MANAGE} run_map + test: @echo "-> Run the test suite" ${ACTIVATE} DJANGO_SETTINGS_MODULE=purldb.settings ${PYTHON_EXE} -m pytest -vvs diff --git a/README.rst b/README.rst index ce211f23..a837a5ac 100644 --- a/README.rst +++ b/README.rst @@ -43,8 +43,23 @@ Once PurlDB and the database has been set up, run tests to ensure functionality: make test -Post-Installation ------------------ +Usage +----- +Start the PurlDB server by running: +:: + + make run + +To start visiting upstream package repositories for package metadata: +:: + + make run_visit + +To populate the PackageDB using visited package metadata: +:: + + make run_map + If you have an empty PackageDB without Package and Package Resource information, ClearCode should be run for a while so it can populate the PackageDB with Package and Package Resource information from clearlydefined. @@ -68,32 +83,40 @@ matching indices from the collected Package data: make index_packages -Usage ------ -Start the PurlDB server by running: -:: +API Endpoints +------------- - make run +* ``api/packages`` + + * Contains all of the Packages stored in the PackageDB + +* ``api/resources`` + + * Contains all of the Resources stored in the PackageDB -You can send a ScanCode JSON scan for matching at the api/match_request/ endpoint using the HTML view or API. +* ``api/cditems`` -There are currently four types of matching that MatchCode provides: + * Contains the visited ClearlyDefined harvests or definitions -* Exact Package archive matching +* ``api/approximate_directory_content_index`` - * Check the SHA1 values of archives from a scan to determine if they are known Packages + * Contains the directory content fingerprints for Packages with Resources + * Used to check if a directory and the files under it is from a known Package using the SHA1 values of the files -* Exact Package file matching +* ``api/approximate_directory_structure_index`` - * Check the SHA1 values of files from a scan to see what Packages also has that file + * Contains the directory structure fingerprints for Packages with Resources + * Used to check if a directory and the files under it is from a known Package using the name of the files -* Approximate Directory structure matching +* ``api/exact_file_index`` - * Check to see if a directory and the files under it is from a known Package using the name of the files + * Contains the SHA1 values of Package Resources + * Used to check the SHA1 values of files from a scan to see what Packages also has that file -* Approximate Directory content matching +* ``api/exact_package_archive_index`` - * Check to see if a directory and the files under it is from a known Package using the SHA1 values of the files + * Contains the SHA1 values of Package archives + * Used to check the SHA1 values of archives from a scan to determine if they are known Packages License From 429722790392fd901f85a44a10d4cf84873d7559 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Fri, 16 Dec 2022 01:57:07 +0000 Subject: [PATCH 27/38] Remove README and CHANGELOG from clearcode Signed-off-by: Jono Yang --- clearcode/CHANGELOG.rst | 10 --- clearcode/README.rst | 189 ---------------------------------------- 2 files changed, 199 deletions(-) delete mode 100644 clearcode/CHANGELOG.rst delete mode 100644 clearcode/README.rst diff --git a/clearcode/CHANGELOG.rst b/clearcode/CHANGELOG.rst deleted file mode 100644 index 16795cfd..00000000 --- a/clearcode/CHANGELOG.rst +++ /dev/null @@ -1,10 +0,0 @@ - -Changelog -========= - - -0.0.3 2020-06-19 ----------------- - -* Initial release - diff --git a/clearcode/README.rst b/clearcode/README.rst deleted file mode 100644 index ce01b711..00000000 --- a/clearcode/README.rst +++ /dev/null @@ -1,189 +0,0 @@ -=============================== -ClearCode toolkit -=============================== - -ClearCode is a simple tool to fetch and sync ClearlyDefined data for a local copy. - -ClearlyDefined data are organized as deeply nested trees of JSON files. - -The data that are synchronized include with this tool include: - - - the "definitions" that contain the aggregated data from running multiple scan - tools and if available a manual expert curation - - the "harvests" that contain the actual detailed output of scans (e.g. scancode runs) - -The items are not fetched for now: - - - the "attachments" that are whole original files such as a README file - - the "deadletters" that are scan failure traces when things fail: these are - not available through the API - - -Here are some stats on the ClearlyDefined data files set as of 2020-02-26, -excluding "deadletters" and most attachments: - -+----------------+-------------+-------------+--------------+-----------------+---------+ -| | JSON Files | Directories | Files & Dirs | Gzipped Size | On disk | -+================+=============+=============+==============+=================+=========+ -| ScanCode scans | 9,087,479 | 29,052,667 | 38,140,146 | 139,754,303,291 | ~400 GB | -+----------------+-------------+-------------+--------------+-----------------+---------+ -| Defs. & misc. | 38,796,760 | 44,825,854 | 83,622,614 | 304,861,913,800 | ~1 TB | -+----------------+-------------+-------------+--------------+-----------------+---------+ -| Total | 47,884,239 | 73,878,521 | 121,762,760 | 444,616,217,091 | ~2 TB | -+----------------+-------------+-------------+--------------+-----------------+---------+ - -Such a large number of files breaks about any filesystem: a mere directory -listing can take days to complete. To avoid these file size and number issues, -the JSON data fetched from the ClearlyDefined API are stored as gzipped-compressed -JSON as blobs in a PosgresSQL database keyed by the file path. -That path is the same as the path used in the ClearlyDefined "blob" storage on Azure. -You can also save these as real files gzipped-compressed JSON files (with the caveat -that this will make the filesystem crumble and this may require a special mkfs -invocation to create a filesystems with enough inodes.) - - -Requirements ------------- - -To run this tool, you need: - -- a POSIX OS (Linux) -- Python 3.6+ -- PosgresSQL 9.5+ if you want to handle a large dataset -- plenty of space, bandwidth and CPU. - - -Quick start using a simple file storage ---------------------------------------- - -Run these commands to get started:: - - $ source configure - $ clearsync --help - -For instance, try this command:: - - $ clearsync --output-dir clearly-local --verbose -n3 - -This will fetch continuously everything (definitions, harvests, etc). from -ClearlyDefined using three processes in parallel and save the output as JSON -files in the clearly-local directory. - -You can abort this at anytime with Ctrl+C. - - -WARNING: this may ceate too many files and directory for your file system sanity. -Consider using the PostgreSQL storage instead. - - -Quick start using a database storage ------------------------------------- - -First create a PostgreSQL database. -This requires sudo access. This is tested on Debian and Ubuntu. -:: - - $ ./createdb.sh - - -Then run these commands to get started:: - - $ source configure - $ clearsync --help - - -For instance, try this command:: - - $ clearsync --save-to-db --verbose -n3 - -This will fetch all the latest data items and save them in the "clearcode" -PostgresDB using three processes in parallel for fetching. -You can abort this at anytime with Ctrl+C. - - -Basic tests can be run with the following command:: - - $ ./manage.py test clearcode --verbosity=2 - - - -Using the Rest API and webserver to import and export items from ClearCode --------------------------------------------------------------------------- - -This assumes you have already populated your database even partially. -In a first shell, start the webserver:: - - $ source configure - $ ./manage.py runserver - -You can then visit the API at http://127.0.0.1:8000/api/ - -In a second shell, you can run the command line API client tool to export data -fetched from ClearlyDefined:: - - $ source configure - $ python etc/scripts/clearcode-api-backup.py \ - --api-root-url http://127.0.0.1:8000/api/ \ - --last-modified-date 2020-06-20 - - Starting backup from http://127.0.0.1:8000/api/ - Collecting cditems... - 821 total - [...........................................................................] - 821 cditems collected. - Backup location: /etc/scripts/clearcode_backup_2020-06-23_00-30-22 - Backup completed. - -The exported backup is saved as a single JSON file in a directory created for -this run named with a timestamp such as clearcode_backup_2020-06-22_21-04-48. - - -In that second shell, you can then run the command line API client tool to -import data saved from the export/backup run above:: - - $ python etc/scripts/clearcode-api-import.py \ - --clearcode-target-api-url http://127.0.0.1:8000/api/ \ - --backup-directory etc/scripts/clearcode_backup_2020-06-23_00-30-22/ - - Importing objects from ../etc/scripts/clearcode_backup_2020-06-23_00-30-22 to http://127.0.0.1:8000/api/ - Copying 821 cditems...........................................Copy completed. - Results saved in /etc/scripts/copy_results_2020-06-23_00-32-37.json - -This would likely something you would run on an isolated ClearCode DB that -you want to keep current with items exported from a live replicating DB. - -Note that these tools have minimal external requirements: only the requests -library and have been designed to be used as single files that can be copied -around. - -See also for help on these two utilities:: - - $ python etc/scripts/clearcode-api-backup.py -h - $ python etc/scripts/clearcode-api-import.py -h - - -Support -------- - -Enter a ticket with bugs, issues or questions at -https://github.com/nexB/clearcode-toolkit/ - -And join us to chat on Gitter (also by IRC) at -https://gitter.im/aboutcode-org/discuss - - -Release TODO ------------- - -- Merge in master and tag release. -- pip install wheel twine -- rm dist/* -- python setup.py release -- twine upload dist/* - - -License -------- - -Apache-2.0 - From 09f97c85503e6bb59c08809a10a30e467188dceb Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Fri, 16 Dec 2022 02:12:27 +0000 Subject: [PATCH 28/38] Rename discovery to minecode * Rename all references to discovery to minecode Signed-off-by: Jono Yang --- clearcode/management/commands/clearload.py | 2 +- clearcode/management/commands/clearsync.py | 2 +- clearindex/harvest.py | 4 +-- .../management/commands/run_clearindex.py | 8 ++--- clearindex/utils.py | 2 +- .../management/commands/index_packages.py | 2 +- matchcode/models.py | 2 +- {discovery => minecode}/__init__.py | 4 +-- {discovery => minecode}/api.py | 2 +- {discovery => minecode}/apps.py | 6 ++-- {discovery => minecode}/bsd-new.LICENSE | 0 {discovery => minecode}/command.py | 2 +- {discovery => minecode}/debutils.py | 0 {discovery => minecode}/ls.py | 0 .../management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/check_licenses.py | 2 +- .../management/commands/check_uri.py | 12 +++---- .../management/commands/dump_purls.py | 0 .../management/commands/get_status.py | 2 +- .../management/commands/remap.py | 2 +- .../management/commands/run_map.py | 16 ++++----- .../management/commands/run_visit.py | 14 ++++---- .../management/commands/seed.py | 10 +++--- {discovery => minecode}/mappers/__init__.py | 0 {discovery => minecode}/mappers/debian.py | 12 +++---- {discovery => minecode}/mappers/fdroid.py | 4 +-- {discovery => minecode}/mappers/freebsd.py | 6 ++-- {discovery => minecode}/mappers/maven.py | 8 ++--- {discovery => minecode}/mappers/npm.py | 4 +-- {discovery => minecode}/mappers/pypi.py | 6 ++-- {discovery => minecode}/mappers/rubygems.py | 8 ++--- .../mappers/sourceforge.py | 4 +-- {discovery => minecode}/mappings/__init__.py | 0 .../mappings/pypi_trove.py | 0 .../migrations/0001_initial.py | 0 .../migrations/0002_auto_20160707_1249.py | 2 +- .../migrations/0003_auto_20160712_1223.py | 2 +- .../migrations/0004_auto_20160713_1731.py | 2 +- .../migrations/0005_resourceuri_metadata.py | 2 +- .../0006_remove_resourceuri_metadata.py | 2 +- ...hange_unique_constraints_on_resourceuri.py | 2 +- ...hange_default_sort_order_on_resourceuri.py | 2 +- .../migrations/0009_resourceuri_source_uri.py | 2 +- .../0010_resourceuri_mining_level.py | 2 +- .../migrations/0011_auto_20170807_0253.py | 2 +- .../migrations/0012_auto_20170807_0444.py | 2 +- .../migrations/0013_auto_20170807_0511.py | 6 ++-- .../migrations/0014_auto_20170807_0529.py | 4 +-- .../migrations/0015_auto_20180607_0851.py | 2 +- .../migrations/0016_remove_resourceuri_sig.py | 2 +- .../migrations/0017_auto_20180619_2236.py | 6 ++-- .../0018_scannableuri_package_id.py | 2 +- .../migrations/0019_auto_20180716_1648.py | 2 +- .../0020_resourceuri_package_url.py | 2 +- .../migrations/0021_auto_20181026_1635.py | 2 +- .../migrations/0022_auto_20190307_2332.py | 10 +++--- .../migrations/__init__.py | 0 {discovery => minecode}/models.py | 8 ++--- {discovery => minecode}/route.py | 0 {discovery => minecode}/rsync.py | 4 +-- {discovery => minecode}/saneyaml.py | 0 {discovery => minecode}/seed.py | 2 +- {discovery => minecode}/tests/__init__.py | 0 {discovery => minecode}/tests/test_command.py | 6 ++-- {discovery => minecode}/tests/test_debian.py | 10 +++--- {discovery => minecode}/tests/test_fdroid.py | 10 +++--- {discovery => minecode}/tests/test_freebsd.py | 8 ++--- .../tests/test_housekeeping.py | 12 +++---- {discovery => minecode}/tests/test_ls.py | 4 +-- {discovery => minecode}/tests/test_maven.py | 16 ++++----- {discovery => minecode}/tests/test_models.py | 10 +++--- {discovery => minecode}/tests/test_npm.py | 12 +++---- {discovery => minecode}/tests/test_pypi.py | 18 +++++----- {discovery => minecode}/tests/test_route.py | 4 +-- {discovery => minecode}/tests/test_rsync.py | 8 ++--- .../tests/test_rubygems.py | 34 +++++++++--------- {discovery => minecode}/tests/test_run_map.py | 16 ++++----- .../tests/test_run_visit.py | 10 +++--- {discovery => minecode}/tests/test_seed.py | 16 ++++----- .../tests/test_sourceforge.py | 8 ++--- {discovery => minecode}/tests/test_utils.py | 6 ++-- {discovery => minecode}/tests/test_version.py | 2 +- .../tests/testfiles/command/bar | 0 .../tests/testfiles/command/foo/.gitignore | 0 .../debian/copyright/basic_copyright | 0 ...pyright-abiword_common_copyright.copyright | 0 .../debian/copyright/invalid_copyright | 0 .../testfiles/debian/debian_lslrs_expected | 0 .../debian/debian_lslrs_on_ubuntu_expected | 0 .../debian/debian_sourceindex_expected | 0 .../debian/debutils/3dldf_2.0.3+dfsg-2.dsc | 0 .../debutils/3dldf_2.0.3+dfsg-2.dsc-expected | 0 .../testfiles/debian/debutils/control_basic | 0 .../testfiles/debian/debutils/control_invalid | 0 .../testfiles/debian/dsc/7kaa_2.14.3-1.dsc | 0 .../debian/dsc/description-expected.json | 0 .../testfiles/debian/dsc/description.json | 0 .../debian/dsc/description_expected.json | 0 .../tests/testfiles/debian/dsc/invalid.dsc | 0 .../testfiles/debian/invalid_files/ls-lR.gz | Bin .../testfiles/debian/lslr/ls-lR_debian.gz | Bin .../debian/lslr/ls-lR_debian.gz-expected.json | 0 .../testfiles/debian/lslr/ls-lR_ubuntu.gz | Bin .../debian/lslr/ls-lR_ubuntu.gz-expected.json | 0 .../testfiles/debian/packages/debian_Packages | 0 .../packages/debian_Packages-expected.json | 0 .../debian_Packages-visit-expected.json | 0 .../testfiles/debian/packages/ubuntu_Packages | 0 .../packages/ubuntu_Packages-expected.json | 0 .../tests/testfiles/debian/release/Release | 0 .../testfiles/debian/release/Release_expected | 0 .../testfiles/debian/release/Release_with_md5 | 0 .../debian/release/Release_with_md5_expected | 0 .../testfiles/debian/release/visited_Release | 0 .../release/visited_Release-expected.json | 0 .../tests/testfiles/debian/sources/Sources.gz | Bin .../debian/sources/Sources.gz-expected.json | 0 .../testfiles/debian/sources/debian_Sources | 0 ...bian_Sources_mapped-expected-packages.json | 0 .../sources/debian_Sources_visit_expected | 0 .../testfiles/debian/sources/ubuntu_Sources | 0 .../sources/ubuntu_Sources_visit_expected | 0 .../testfiles/debian/status/simple_status | 0 .../tests/testfiles/directories/find-ls | 0 .../directories/find-ls-apache-start | 0 .../find-ls-apache-start-expected.json | 0 .../directories/find-ls-expected.json | 0 .../tests/testfiles/directories/ls-lr | 0 .../testfiles/directories/ls-lr-expected.json | 0 .../tests/testfiles/directories/ls-lr-ubuntu | 0 .../directories/ls-lr-ubuntu-expected.json | 0 .../fdroid/index-v2-expected-visit.json | 0 .../index-v2-visited-expected-mapped.json | 0 .../testfiles/fdroid/index-v2-visited.json | 0 .../tests/testfiles/fdroid/index-v2.json | 0 .../freebsd/FreeBSD-10-i386_release_0_.html | 0 .../FreeBSD-10-i386_release_0_.html_expected | 0 .../tests/testfiles/freebsd/FreeBSD.org.html | 0 .../freebsd/FreeBSD.org.html_expected | 0 .../testfiles/freebsd/indexfile_expected | 0 .../freebsd/indexfile_expected_mapper.json | 0 .../tests/testfiles/freebsd/mapper_input1 | 0 .../tests/testfiles/freebsd/packagesite.txz | Bin .../tests/testfiles/freebsd/pkg-devel_index | 0 .../freebsd/pkg-devel_index_mapper.json | 0 .../freebsd/squirrelmail-plugins-1.0_2.txz | Bin .../bytejta-supports-0.5.0-ALPHA4.pom | 0 ...orts-0.5.0-ALPHA4.pom_search_expected.json | 0 .../declared_license_search_expected.json | 0 .../housekeeping/example_expected.json | 0 .../ignore_upper_case_search_expected.json | 0 .../license_expression_search_expected.json | 0 .../end2end/bytejta-supports-0.5.0-ALPHA4.pom | 0 .../end2end/expected_mapped_packages.json | 0 .../maven/end2end/expected_visited_uris.json | 0 .../testfiles/maven/end2end/test_uris.json | 0 .../expected_visited_increment_index.json | 0 .../end2end_index/expected_visited_index.json | 0 .../nexus-maven-repository-index.163.gz | Bin .../nexus-maven-repository-index.properties | 0 .../commons-jaxrs-1.21-index-data.json | 0 .../commons-jaxrs-1.21-pom-data.json | 0 .../end2end_multisteps/commons-jaxrs-1.21.pom | 0 ..._mapped_commons-jaxrs-1.21-from-index.json | 0 ...ed_mapped_commons-jaxrs-1.21-from-pom.json | 0 .../end2end_unicode/commons-jaxrs-1.22.pom | 0 .../expected_mapped_commons-jaxrs-1.22.json | 0 .../expected_visited_commons-jaxrs-1.22.json | 0 .../tests/testfiles/maven/html/app.html | 0 .../maven/html/jcenter.bintray.com.html | 0 .../maven/html/stateframework-compiler.html | 0 .../maven/html/visitor_expected_app.html.json | 0 ...or_expected_jcenter.bintray.com2.html.json | 0 ...expected_stateframework-compiler.html.json | 0 .../buggy/expected_artifacts-defaults.json | 0 .../maven/index/buggy/expected_artifacts.json | 0 .../maven/index/buggy/expected_entries.json | 0 .../maven/index/buggy/expected_uris.json | 0 .../index/buggy/expected_visited_uris.json | 0 .../buggy/nexus-maven-repository-index.gz | Bin .../index/expected_artifacts-all-worthy.json | 0 .../index/expected_artifacts-defaults.json | 0 .../maven/index/expected_artifacts.json | 0 .../maven/index/expected_entries.json | 0 .../testfiles/maven/index/expected_uris.json | 0 .../expected_artifacts-defaults.json | 0 .../index/increment/expected_artifacts.json | 0 .../index/increment/expected_entries.json | 0 .../increment/expected_properties_uris.json | 0 .../maven/index/increment/expected_uris.json | 0 .../nexus-maven-repository-index.445.gz | Bin .../nexus-maven-repository-index.properties | 0 .../increment2/expected_mini_package.json | 0 .../maven/index/increment2/expected_uris.json | 0 .../nexus-maven-repository-index.457.gz | Bin .../index/nexus-maven-repository-index.gz | Bin .../testfiles/maven/mapper/ant-1.6.5.pom | 0 .../testfiles/maven/mapper/ant-1.6.5.pom.json | 0 .../tests/testfiles/maven/mapper/axis-1.4.pom | 0 .../maven/mapper/axis-1.4.pom.package.json | 0 .../maven/mapper/commons-jaxrs-1.21.pom | 0 .../commons-jaxrs-1.21.pom.package.json | 0 .../maven/mapper/commons-pool-1.5.7.pom | 0 .../commons-pool-1.5.7.pom.package.json | 0 .../maven/mapper/depgraph-view-0.1.pom | 0 .../maven/mapper/maven-all-1.0-RELEASE.pom | 0 .../maven-all-1.0-RELEASE.pom.package.json | 0 .../mapper/mysql-connector-java-5.1.27.pom | 0 ...sql-connector-java-5.1.27.pom.package.json | 0 .../maven/mapper/struts-menu-2.4.2.pom | 0 .../mapper/struts-menu-2.4.2.pom.package.json | 0 .../testfiles/maven/mapper/xbean-jmx-2.0.pom | 0 .../mapper/xbean-jmx-2.0.pom.package.json | 0 .../maven-metadata/expected_maven_xml.json | 0 .../maven/maven-metadata/maven-metadata.xml | 0 .../parsing/empty/common-object-1.0.2.pom | 0 .../common-object-1.0.2.pom.package.json | 0 .../maven/parsing/empty/osgl-http-1.1.2.pom | 0 .../empty/osgl-http-1.1.2.pom.package.json | 0 .../parsing/loop/argus-webservices-2.7.0.pom | 0 .../argus-webservices-2.7.0.pom.package.json | 0 .../parsing/loop/argus-webservices-2.8.0.pom | 0 .../argus-webservices-2.8.0.pom.package.json | 0 .../maven/parsing/loop/coreplugin-1.0.0.pom | 0 .../loop/coreplugin-1.0.0.pom.package.json | 0 .../loop/jacuzzi-annotations-0.2.1.pom | 0 ...jacuzzi-annotations-0.2.1.pom.package.json | 0 .../parsing/loop/jacuzzi-database-0.2.1.pom | 0 .../jacuzzi-database-0.2.1.pom.package.json | 0 .../parsing/loop/ojcms-beans-0.1-beta.pom | 0 .../ojcms-beans-0.1-beta.pom.package.json | 0 .../maven/parsing/loop/pkg-2.0.13.1005.pom | 0 .../loop/pkg-2.0.13.1005.pom.package.json | 0 .../maven/parsing/parse/jds-2.17.0718b.pom | 0 .../parse/jds-2.17.0718b.pom.package.json | 0 .../maven/parsing/parse/jds-3.0.1.pom | 0 .../parsing/parse/jds-3.0.1.pom.package.json | 0 .../parse/maven-javanet-plugin-1.7.pom | 0 .../maven-javanet-plugin-1.7.pom.package.json | 0 ...ringmvc-rest-docs-maven-plugin-1.0-RC1.pom | 0 ...docs-maven-plugin-1.0-RC1.pom.package.json | 0 .../maven/pom/classworlds-1.1-alpha-2.pom | 0 .../tests/testfiles/npm/0flux.json | 0 .../testfiles/npm/0flux_npm_expected.json | 0 .../tests/testfiles/npm/1000_records.json | 0 .../tests/testfiles/npm/2112.json | 0 .../testfiles/npm/29_record_expected.json | 0 .../testfiles/npm/554_record_expected.json | 0 .../testfiles/npm/expected_1000_records.json | 0 .../npm/expected_doclimit_visitor.json | 0 .../npm/expected_npmdownload_data_vistor.json | 0 .../npm/expected_npmdownloadvistor.json | 0 .../npm/expected_npmindexvisitor.json | 0 .../testfiles/npm/expected_over_limit.json | 0 .../testfiles/npm/expected_ticket_439.json | 0 .../testfiles/npm/expected_ticket_440.json | 0 .../tests/testfiles/npm/grunticon-sass.json | 0 .../testfiles/npm/jsonp-filter-expected.json | 0 .../tests/testfiles/npm/jsonp-filter.json | 0 .../testfiles/npm/mapper/index.expected.json | 0 .../tests/testfiles/npm/mapper/index.json | 0 .../npm/microdata-node_expected.json | 0 .../tests/testfiles/npm/microdata.json | 0 .../testfiles/npm/npm_2112_expected.json | 0 .../tests/testfiles/npm/over_limit.json | 0 .../tests/testfiles/npm/replicate_doc1.json | 0 .../tests/testfiles/npm/ticket_439.json | 0 .../testfiles/npm/ticket_440_records.json | 0 .../testfiles/pypi/boolean.py-2.0.dev3.json | 0 .../tests/testfiles/pypi/boolean.py.json | 0 .../tests/testfiles/pypi/cage.json | 0 .../tests/testfiles/pypi/cage_1.1.2.json | 0 .../tests/testfiles/pypi/cage_1.1.3.json | 0 .../testfiles/pypi/expected-CAGE-1.1.2.json | 0 .../testfiles/pypi/expected-CAGE-1.1.3.json | 0 .../pypi/expected-boolean.py-2.0.dev3.json | 0 .../testfiles/pypi/expected-lxml-3.2.0.json | 0 .../expected_data-boolean.py-2.0.dev3.json | 0 .../pypi/expected_data-cage_1.1.2.json | 0 .../pypi/expected_data-cage_1.1.3.json | 0 .../testfiles/pypi/expected_uri_visitor1.json | 0 .../testfiles/pypi/expected_uri_visitor2.json | 0 .../expected_uris-boolean.py-2.0.dev3.json | 0 .../pypi/expected_uris-boolean.py.json | 0 .../testfiles/pypi/expected_uris-cage.json | 0 .../pypi/expected_uris-cage_1.1.2.json | 0 .../pypi/expected_uris-cage_1.1.3.json | 0 .../tests/testfiles/pypi/lxml-3.2.0.json | 0 .../tests/testfiles/pypi/map/3to2-1.1.1.json | 0 .../pypi/map/expected-3to2-1.1.1.json | 0 .../pypi/pypiindexvisitor-expected.json | 0 .../tests/testfiles/rsync/rsync_dev.dir | 0 .../testfiles/rsync/rsync_dir/bar/that/baz | 0 .../tests/testfiles/rsync/rsync_dir/bar/this | 0 .../tests/testfiles/rsync/rsync_dir/foo | 0 .../tests/testfiles/rsync/rsync_modules | 0 .../rsync/rsync_v3.0.9_protocol30.dir | 0 .../rsync/rsync_v3.1.0_protocol31.dir | 0 .../tests/testfiles/rsync/rsync_wicket.dir | 0 .../testfiles/rubygems/0mq-0.4.1.gem.metadata | 0 .../rubygems/0mq-0.4.1.gem.package.json | 0 .../tests/testfiles/rubygems/a_okay-0.1.0.gem | Bin .../rubygems/a_okay-0.1.0.gem.metadata | 0 .../rubygems/a_okay-0.1.0.gem.package.json | 0 .../rubygems/action_tracker-1.0.2.gem | Bin .../action_tracker-1.0.2.gem.package.json | 0 .../rubygems/apiv1/0xffffff.api.json | 0 .../rubygems/apiv1/0xffffff.api.package.json | 0 .../rubygems/apiv1/a1630ty_a1630ty.api.json | 0 .../apiv1/a1630ty_a1630ty.api.mapped.json | 0 .../apiv1/a1630ty_a1630ty.api.package.json | 0 .../rubygems/apiv1/action_tracker.api.json | 0 .../apiv1/action_tracker.api.package.json | 0 .../rubygems/apiv1/expected_0xffffff.api.json | 0 .../apiv1/expected_a1630ty_a1630ty.api.json | 0 .../rubygems/apiv1/expected_zuck.api.json | 0 .../testfiles/rubygems/apiv1/zuck.api.json | 0 .../rubygems/apiv1/zuck.api.package.json | 0 .../rubygems/archive-tar-minitar-0.5.2.gem | Bin ...archive-tar-minitar-0.5.2.gem.package.json | 0 .../testfiles/rubygems/blankslate-3.1.3.gem | Bin .../blankslate-3.1.3.gem.package.json | 0 .../gemspec/address_standardization.gemspec | 0 .../testfiles/rubygems/gemspec/arel.gemspec | 0 .../rubygems/index/latest_specs.4.8.gz | Bin .../index/latest_specs.4.8.gz.expected.json | 0 .../tests/testfiles/rubygems/m2r-2.1.0.gem | Bin .../rubygems/m2r-2.1.0.gem.package.json | 0 ...mallidea-address_standardization-0.4.1.gem | Bin ...ress_standardization-0.4.1.gem.mapped.json | 0 ...address_standardization-0.4.1.gem.metadata | 0 ...ess_standardization-0.4.1.gem.package.json | 0 .../mysmallidea-mad_mimi_mailer-0.0.9.gem | Bin ...dea-mad_mimi_mailer-0.0.9.gem.package.json | 0 .../rubygems/ng-rails-csrf-0.1.0.gem | Bin .../ng-rails-csrf-0.1.0.gem.package.json | 0 .../tests/testfiles/rubygems/small-0.2.gem | Bin .../rubygems/small-0.2.gem.package.json | 0 .../rubygems/small_wonder-0.1.10.gem | Bin .../small_wonder-0.1.10.gem.package.json | 0 .../rubygems/sprockets-vendor_gems-0.1.3.gem | Bin ...prockets-vendor_gems-0.1.3.gem.mapped.json | 0 ...rockets-vendor_gems-0.1.3.gem.package.json | 0 ...rockets-vendor_gems-0.1.3.gem.visited.json | 0 ...pdate_with_same_mining_level-expected.json | 0 ...with_higher_new_mining_level-expected.json | 0 ...with_lesser_new_mining_level-expected.json | 0 ...st_merge_packages_no_replace-expected.json | 0 ..._merge_packages_with_replace-expected.json | 0 .../sourceforge/a-vitkus_profile.html | 0 .../expected_heanet_rsync_dir.json | 0 .../sourceforge/expected_netwiki.json | 0 .../sourceforge/expected_sf_dir_index.json | 0 .../sourceforge/expected_sf_dir_page.json | 0 .../sourceforge/expected_sf_project.json | 0 .../sourceforge/expected_sf_sitemap.json | 0 .../sourceforge/expected_sf_sitemap_new.json | 0 .../sourceforge/expected_sf_sitemap_page.json | 0 .../expected_sf_sitemap_page_new.json | 0 .../sourceforge/expected_sitemap-6.json | 0 .../testfiles/sourceforge/filezilla.json | 0 .../sourceforge/mapper_niftyphp_expected.json | 0 .../sourceforge/mapper_odanur_expected.json | 0 .../sourceforge/mapper_omonoql_expected.json | 0 .../mapper_openstunts_expected.json | 0 .../tests/testfiles/sourceforge/monoql.json | 0 .../tests/testfiles/sourceforge/netwiki.json | 0 .../tests/testfiles/sourceforge/niftyphp.json | 0 .../tests/testfiles/sourceforge/odanur.json | 0 .../testfiles/sourceforge/openstunts.json | 0 .../sourceforge/rsync_heanet_sfnet.dir | 0 .../tests/testfiles/sourceforge/sitemap-1.xml | 0 .../tests/testfiles/sourceforge/sitemap-6.xml | 0 .../tests/testfiles/sourceforge/sitemap.xml | 0 {discovery => minecode}/utils.py | 2 +- {discovery => minecode}/utils_test.py | 5 +-- {discovery => minecode}/utils_test.py.ABOUT | 0 {discovery => minecode}/utils_test.py.NOTICE | 0 {discovery => minecode}/version.py | 0 {discovery => minecode}/visitors/__init__.py | 4 +-- {discovery => minecode}/visitors/debian.py | 12 +++---- {discovery => minecode}/visitors/fdroid.py | 14 ++++---- {discovery => minecode}/visitors/freebsd.py | 12 +++---- .../visitors/java_stream.LICENSE | 0 .../visitors/java_stream.py | 0 .../visitors/java_stream.py.ABOUT | 0 {discovery => minecode}/visitors/maven.py | 12 +++---- {discovery => minecode}/visitors/npm.py | 8 ++--- {discovery => minecode}/visitors/pypi.py | 12 +++---- {discovery => minecode}/visitors/rubygems.py | 12 +++---- .../visitors/sourceforge.py | 12 +++---- purldb/settings.py | 4 +-- 393 files changed, 268 insertions(+), 271 deletions(-) rename {discovery => minecode}/__init__.py (88%) rename {discovery => minecode}/api.py (94%) rename {discovery => minecode}/apps.py (82%) rename {discovery => minecode}/bsd-new.LICENSE (100%) rename {discovery => minecode}/command.py (98%) rename {discovery => minecode}/debutils.py (100%) rename {discovery => minecode}/ls.py (100%) rename {discovery => minecode}/management/__init__.py (100%) rename {discovery => minecode}/management/commands/__init__.py (100%) rename {discovery => minecode}/management/commands/check_licenses.py (98%) rename {discovery => minecode}/management/commands/check_uri.py (94%) rename {discovery => minecode}/management/commands/dump_purls.py (100%) rename {discovery => minecode}/management/commands/get_status.py (97%) rename {discovery => minecode}/management/commands/remap.py (97%) rename {discovery => minecode}/management/commands/run_map.py (98%) rename {discovery => minecode}/management/commands/run_visit.py (97%) rename {discovery => minecode}/management/commands/seed.py (93%) rename {discovery => minecode}/mappers/__init__.py (100%) rename {discovery => minecode}/mappers/debian.py (98%) rename {discovery => minecode}/mappers/fdroid.py (98%) rename {discovery => minecode}/mappers/freebsd.py (94%) rename {discovery => minecode}/mappers/maven.py (96%) rename {discovery => minecode}/mappers/npm.py (96%) rename {discovery => minecode}/mappers/pypi.py (98%) rename {discovery => minecode}/mappers/rubygems.py (98%) rename {discovery => minecode}/mappers/sourceforge.py (98%) rename {discovery => minecode}/mappings/__init__.py (100%) rename {discovery => minecode}/mappings/pypi_trove.py (100%) rename {discovery => minecode}/migrations/0001_initial.py (100%) rename {discovery => minecode}/migrations/0002_auto_20160707_1249.py (99%) rename {discovery => minecode}/migrations/0003_auto_20160712_1223.py (95%) rename {discovery => minecode}/migrations/0004_auto_20160713_1731.py (90%) rename {discovery => minecode}/migrations/0005_resourceuri_metadata.py (92%) rename {discovery => minecode}/migrations/0006_remove_resourceuri_metadata.py (87%) rename {discovery => minecode}/migrations/0007_change_unique_constraints_on_resourceuri.py (92%) rename {discovery => minecode}/migrations/0008_change_default_sort_order_on_resourceuri.py (85%) rename {discovery => minecode}/migrations/0009_resourceuri_source_uri.py (91%) rename {discovery => minecode}/migrations/0010_resourceuri_mining_level.py (91%) rename {discovery => minecode}/migrations/0011_auto_20170807_0253.py (95%) rename {discovery => minecode}/migrations/0012_auto_20170807_0444.py (88%) rename {discovery => minecode}/migrations/0013_auto_20170807_0511.py (72%) rename {discovery => minecode}/migrations/0014_auto_20170807_0529.py (81%) rename {discovery => minecode}/migrations/0015_auto_20180607_0851.py (92%) rename {discovery => minecode}/migrations/0016_remove_resourceuri_sig.py (87%) rename {discovery => minecode}/migrations/0017_auto_20180619_2236.py (96%) rename {discovery => minecode}/migrations/0018_scannableuri_package_id.py (90%) rename {discovery => minecode}/migrations/0019_auto_20180716_1648.py (91%) rename {discovery => minecode}/migrations/0020_resourceuri_package_url.py (91%) rename {discovery => minecode}/migrations/0021_auto_20181026_1635.py (93%) rename {discovery => minecode}/migrations/0022_auto_20190307_2332.py (69%) rename {discovery => minecode}/migrations/__init__.py (100%) rename {discovery => minecode}/models.py (99%) rename {discovery => minecode}/route.py (100%) rename {discovery => minecode}/rsync.py (98%) rename {discovery => minecode}/saneyaml.py (100%) rename {discovery => minecode}/seed.py (97%) rename {discovery => minecode}/tests/__init__.py (100%) rename {discovery => minecode}/tests/test_command.py (89%) rename {discovery => minecode}/tests/test_debian.py (98%) rename {discovery => minecode}/tests/test_fdroid.py (88%) rename {discovery => minecode}/tests/test_freebsd.py (93%) rename {discovery => minecode}/tests/test_housekeeping.py (93%) rename {discovery => minecode}/tests/test_ls.py (97%) rename {discovery => minecode}/tests/test_maven.py (98%) rename {discovery => minecode}/tests/test_models.py (99%) rename {discovery => minecode}/tests/test_npm.py (97%) rename {discovery => minecode}/tests/test_pypi.py (96%) rename {discovery => minecode}/tests/test_route.py (99%) rename {discovery => minecode}/tests/test_rsync.py (98%) rename {discovery => minecode}/tests/test_rubygems.py (92%) rename {discovery => minecode}/tests/test_run_map.py (98%) rename {discovery => minecode}/tests/test_run_visit.py (98%) rename {discovery => minecode}/tests/test_seed.py (95%) rename {discovery => minecode}/tests/test_sourceforge.py (96%) rename {discovery => minecode}/tests/test_utils.py (93%) rename {discovery => minecode}/tests/test_version.py (99%) rename {discovery => minecode}/tests/testfiles/command/bar (100%) rename {discovery => minecode}/tests/testfiles/command/foo/.gitignore (100%) rename {discovery => minecode}/tests/testfiles/debian/copyright/basic_copyright (100%) rename {discovery => minecode}/tests/testfiles/debian/copyright/copyright_abiword_common_copyright-abiword_common_copyright.copyright (100%) rename {discovery => minecode}/tests/testfiles/debian/copyright/invalid_copyright (100%) rename {discovery => minecode}/tests/testfiles/debian/debian_lslrs_expected (100%) rename {discovery => minecode}/tests/testfiles/debian/debian_lslrs_on_ubuntu_expected (100%) rename {discovery => minecode}/tests/testfiles/debian/debian_sourceindex_expected (100%) rename {discovery => minecode}/tests/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc (100%) rename {discovery => minecode}/tests/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc-expected (100%) rename {discovery => minecode}/tests/testfiles/debian/debutils/control_basic (100%) rename {discovery => minecode}/tests/testfiles/debian/debutils/control_invalid (100%) rename {discovery => minecode}/tests/testfiles/debian/dsc/7kaa_2.14.3-1.dsc (100%) rename {discovery => minecode}/tests/testfiles/debian/dsc/description-expected.json (100%) rename {discovery => minecode}/tests/testfiles/debian/dsc/description.json (100%) rename {discovery => minecode}/tests/testfiles/debian/dsc/description_expected.json (100%) rename {discovery => minecode}/tests/testfiles/debian/dsc/invalid.dsc (100%) rename {discovery => minecode}/tests/testfiles/debian/invalid_files/ls-lR.gz (100%) rename {discovery => minecode}/tests/testfiles/debian/lslr/ls-lR_debian.gz (100%) rename {discovery => minecode}/tests/testfiles/debian/lslr/ls-lR_debian.gz-expected.json (100%) rename {discovery => minecode}/tests/testfiles/debian/lslr/ls-lR_ubuntu.gz (100%) rename {discovery => minecode}/tests/testfiles/debian/lslr/ls-lR_ubuntu.gz-expected.json (100%) rename {discovery => minecode}/tests/testfiles/debian/packages/debian_Packages (100%) rename {discovery => minecode}/tests/testfiles/debian/packages/debian_Packages-expected.json (100%) rename {discovery => minecode}/tests/testfiles/debian/packages/debian_Packages-visit-expected.json (100%) rename {discovery => minecode}/tests/testfiles/debian/packages/ubuntu_Packages (100%) rename {discovery => minecode}/tests/testfiles/debian/packages/ubuntu_Packages-expected.json (100%) rename {discovery => minecode}/tests/testfiles/debian/release/Release (100%) rename {discovery => minecode}/tests/testfiles/debian/release/Release_expected (100%) rename {discovery => minecode}/tests/testfiles/debian/release/Release_with_md5 (100%) rename {discovery => minecode}/tests/testfiles/debian/release/Release_with_md5_expected (100%) rename {discovery => minecode}/tests/testfiles/debian/release/visited_Release (100%) rename {discovery => minecode}/tests/testfiles/debian/release/visited_Release-expected.json (100%) rename {discovery => minecode}/tests/testfiles/debian/sources/Sources.gz (100%) rename {discovery => minecode}/tests/testfiles/debian/sources/Sources.gz-expected.json (100%) rename {discovery => minecode}/tests/testfiles/debian/sources/debian_Sources (100%) rename {discovery => minecode}/tests/testfiles/debian/sources/debian_Sources_mapped-expected-packages.json (100%) rename {discovery => minecode}/tests/testfiles/debian/sources/debian_Sources_visit_expected (100%) rename {discovery => minecode}/tests/testfiles/debian/sources/ubuntu_Sources (100%) rename {discovery => minecode}/tests/testfiles/debian/sources/ubuntu_Sources_visit_expected (100%) rename {discovery => minecode}/tests/testfiles/debian/status/simple_status (100%) rename {discovery => minecode}/tests/testfiles/directories/find-ls (100%) rename {discovery => minecode}/tests/testfiles/directories/find-ls-apache-start (100%) rename {discovery => minecode}/tests/testfiles/directories/find-ls-apache-start-expected.json (100%) rename {discovery => minecode}/tests/testfiles/directories/find-ls-expected.json (100%) rename {discovery => minecode}/tests/testfiles/directories/ls-lr (100%) rename {discovery => minecode}/tests/testfiles/directories/ls-lr-expected.json (100%) rename {discovery => minecode}/tests/testfiles/directories/ls-lr-ubuntu (100%) rename {discovery => minecode}/tests/testfiles/directories/ls-lr-ubuntu-expected.json (100%) rename {discovery => minecode}/tests/testfiles/fdroid/index-v2-expected-visit.json (100%) rename {discovery => minecode}/tests/testfiles/fdroid/index-v2-visited-expected-mapped.json (100%) rename {discovery => minecode}/tests/testfiles/fdroid/index-v2-visited.json (100%) rename {discovery => minecode}/tests/testfiles/fdroid/index-v2.json (100%) rename {discovery => minecode}/tests/testfiles/freebsd/FreeBSD-10-i386_release_0_.html (100%) rename {discovery => minecode}/tests/testfiles/freebsd/FreeBSD-10-i386_release_0_.html_expected (100%) rename {discovery => minecode}/tests/testfiles/freebsd/FreeBSD.org.html (100%) rename {discovery => minecode}/tests/testfiles/freebsd/FreeBSD.org.html_expected (100%) rename {discovery => minecode}/tests/testfiles/freebsd/indexfile_expected (100%) rename {discovery => minecode}/tests/testfiles/freebsd/indexfile_expected_mapper.json (100%) rename {discovery => minecode}/tests/testfiles/freebsd/mapper_input1 (100%) rename {discovery => minecode}/tests/testfiles/freebsd/packagesite.txz (100%) rename {discovery => minecode}/tests/testfiles/freebsd/pkg-devel_index (100%) rename {discovery => minecode}/tests/testfiles/freebsd/pkg-devel_index_mapper.json (100%) rename {discovery => minecode}/tests/testfiles/freebsd/squirrelmail-plugins-1.0_2.txz (100%) rename {discovery => minecode}/tests/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom (100%) rename {discovery => minecode}/tests/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom_search_expected.json (100%) rename {discovery => minecode}/tests/testfiles/housekeeping/declared_license_search_expected.json (100%) rename {discovery => minecode}/tests/testfiles/housekeeping/example_expected.json (100%) rename {discovery => minecode}/tests/testfiles/housekeeping/ignore_upper_case_search_expected.json (100%) rename {discovery => minecode}/tests/testfiles/housekeeping/license_expression_search_expected.json (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end/bytejta-supports-0.5.0-ALPHA4.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end/expected_mapped_packages.json (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end/expected_visited_uris.json (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end/test_uris.json (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end_index/expected_visited_increment_index.json (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end_index/expected_visited_index.json (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end_index/nexus-maven-repository-index.163.gz (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end_index/nexus-maven-repository-index.properties (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-index-data.json (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-pom-data.json (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-index.json (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-pom.json (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end_unicode/commons-jaxrs-1.22.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end_unicode/expected_mapped_commons-jaxrs-1.22.json (100%) rename {discovery => minecode}/tests/testfiles/maven/end2end_unicode/expected_visited_commons-jaxrs-1.22.json (100%) rename {discovery => minecode}/tests/testfiles/maven/html/app.html (100%) rename {discovery => minecode}/tests/testfiles/maven/html/jcenter.bintray.com.html (100%) rename {discovery => minecode}/tests/testfiles/maven/html/stateframework-compiler.html (100%) rename {discovery => minecode}/tests/testfiles/maven/html/visitor_expected_app.html.json (100%) rename {discovery => minecode}/tests/testfiles/maven/html/visitor_expected_jcenter.bintray.com2.html.json (100%) rename {discovery => minecode}/tests/testfiles/maven/html/visitor_expected_stateframework-compiler.html.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/buggy/expected_artifacts-defaults.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/buggy/expected_artifacts.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/buggy/expected_entries.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/buggy/expected_uris.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/buggy/expected_visited_uris.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/buggy/nexus-maven-repository-index.gz (100%) rename {discovery => minecode}/tests/testfiles/maven/index/expected_artifacts-all-worthy.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/expected_artifacts-defaults.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/expected_artifacts.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/expected_entries.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/expected_uris.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/increment/expected_artifacts-defaults.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/increment/expected_artifacts.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/increment/expected_entries.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/increment/expected_properties_uris.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/increment/expected_uris.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/increment/nexus-maven-repository-index.445.gz (100%) rename {discovery => minecode}/tests/testfiles/maven/index/increment/nexus-maven-repository-index.properties (100%) rename {discovery => minecode}/tests/testfiles/maven/index/increment2/expected_mini_package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/increment2/expected_uris.json (100%) rename {discovery => minecode}/tests/testfiles/maven/index/increment2/nexus-maven-repository-index.457.gz (100%) rename {discovery => minecode}/tests/testfiles/maven/index/nexus-maven-repository-index.gz (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/ant-1.6.5.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/ant-1.6.5.pom.json (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/axis-1.4.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/axis-1.4.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/commons-jaxrs-1.21.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/commons-jaxrs-1.21.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/commons-pool-1.5.7.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/commons-pool-1.5.7.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/depgraph-view-0.1.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/struts-menu-2.4.2.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/struts-menu-2.4.2.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/xbean-jmx-2.0.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/mapper/xbean-jmx-2.0.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/maven-metadata/expected_maven_xml.json (100%) rename {discovery => minecode}/tests/testfiles/maven/maven-metadata/maven-metadata.xml (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/empty/common-object-1.0.2.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/empty/common-object-1.0.2.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/parse/jds-2.17.0718b.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/parse/jds-2.17.0718b.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/parse/jds-3.0.1.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/parse/jds-3.0.1.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom (100%) rename {discovery => minecode}/tests/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom.package.json (100%) rename {discovery => minecode}/tests/testfiles/maven/pom/classworlds-1.1-alpha-2.pom (100%) rename {discovery => minecode}/tests/testfiles/npm/0flux.json (100%) rename {discovery => minecode}/tests/testfiles/npm/0flux_npm_expected.json (100%) rename {discovery => minecode}/tests/testfiles/npm/1000_records.json (100%) rename {discovery => minecode}/tests/testfiles/npm/2112.json (100%) rename {discovery => minecode}/tests/testfiles/npm/29_record_expected.json (100%) rename {discovery => minecode}/tests/testfiles/npm/554_record_expected.json (100%) rename {discovery => minecode}/tests/testfiles/npm/expected_1000_records.json (100%) rename {discovery => minecode}/tests/testfiles/npm/expected_doclimit_visitor.json (100%) rename {discovery => minecode}/tests/testfiles/npm/expected_npmdownload_data_vistor.json (100%) rename {discovery => minecode}/tests/testfiles/npm/expected_npmdownloadvistor.json (100%) rename {discovery => minecode}/tests/testfiles/npm/expected_npmindexvisitor.json (100%) rename {discovery => minecode}/tests/testfiles/npm/expected_over_limit.json (100%) rename {discovery => minecode}/tests/testfiles/npm/expected_ticket_439.json (100%) rename {discovery => minecode}/tests/testfiles/npm/expected_ticket_440.json (100%) rename {discovery => minecode}/tests/testfiles/npm/grunticon-sass.json (100%) rename {discovery => minecode}/tests/testfiles/npm/jsonp-filter-expected.json (100%) rename {discovery => minecode}/tests/testfiles/npm/jsonp-filter.json (100%) rename {discovery => minecode}/tests/testfiles/npm/mapper/index.expected.json (100%) rename {discovery => minecode}/tests/testfiles/npm/mapper/index.json (100%) rename {discovery => minecode}/tests/testfiles/npm/microdata-node_expected.json (100%) rename {discovery => minecode}/tests/testfiles/npm/microdata.json (100%) rename {discovery => minecode}/tests/testfiles/npm/npm_2112_expected.json (100%) rename {discovery => minecode}/tests/testfiles/npm/over_limit.json (100%) rename {discovery => minecode}/tests/testfiles/npm/replicate_doc1.json (100%) rename {discovery => minecode}/tests/testfiles/npm/ticket_439.json (100%) rename {discovery => minecode}/tests/testfiles/npm/ticket_440_records.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/boolean.py-2.0.dev3.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/boolean.py.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/cage.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/cage_1.1.2.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/cage_1.1.3.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/expected-CAGE-1.1.2.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/expected-CAGE-1.1.3.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/expected-boolean.py-2.0.dev3.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/expected-lxml-3.2.0.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/expected_data-boolean.py-2.0.dev3.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/expected_data-cage_1.1.2.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/expected_data-cage_1.1.3.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/expected_uri_visitor1.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/expected_uri_visitor2.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/expected_uris-boolean.py-2.0.dev3.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/expected_uris-boolean.py.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/expected_uris-cage.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/expected_uris-cage_1.1.2.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/expected_uris-cage_1.1.3.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/lxml-3.2.0.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/map/3to2-1.1.1.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/map/expected-3to2-1.1.1.json (100%) rename {discovery => minecode}/tests/testfiles/pypi/pypiindexvisitor-expected.json (100%) rename {discovery => minecode}/tests/testfiles/rsync/rsync_dev.dir (100%) rename {discovery => minecode}/tests/testfiles/rsync/rsync_dir/bar/that/baz (100%) rename {discovery => minecode}/tests/testfiles/rsync/rsync_dir/bar/this (100%) rename {discovery => minecode}/tests/testfiles/rsync/rsync_dir/foo (100%) rename {discovery => minecode}/tests/testfiles/rsync/rsync_modules (100%) rename {discovery => minecode}/tests/testfiles/rsync/rsync_v3.0.9_protocol30.dir (100%) rename {discovery => minecode}/tests/testfiles/rsync/rsync_v3.1.0_protocol31.dir (100%) rename {discovery => minecode}/tests/testfiles/rsync/rsync_wicket.dir (100%) rename {discovery => minecode}/tests/testfiles/rubygems/0mq-0.4.1.gem.metadata (100%) rename {discovery => minecode}/tests/testfiles/rubygems/0mq-0.4.1.gem.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/a_okay-0.1.0.gem (100%) rename {discovery => minecode}/tests/testfiles/rubygems/a_okay-0.1.0.gem.metadata (100%) rename {discovery => minecode}/tests/testfiles/rubygems/a_okay-0.1.0.gem.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/action_tracker-1.0.2.gem (100%) rename {discovery => minecode}/tests/testfiles/rubygems/action_tracker-1.0.2.gem.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/apiv1/0xffffff.api.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/apiv1/0xffffff.api.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.mapped.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/apiv1/action_tracker.api.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/apiv1/action_tracker.api.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/apiv1/expected_0xffffff.api.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/apiv1/expected_a1630ty_a1630ty.api.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/apiv1/expected_zuck.api.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/apiv1/zuck.api.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/apiv1/zuck.api.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/archive-tar-minitar-0.5.2.gem (100%) rename {discovery => minecode}/tests/testfiles/rubygems/archive-tar-minitar-0.5.2.gem.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/blankslate-3.1.3.gem (100%) rename {discovery => minecode}/tests/testfiles/rubygems/blankslate-3.1.3.gem.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/gemspec/address_standardization.gemspec (100%) rename {discovery => minecode}/tests/testfiles/rubygems/gemspec/arel.gemspec (100%) rename {discovery => minecode}/tests/testfiles/rubygems/index/latest_specs.4.8.gz (100%) rename {discovery => minecode}/tests/testfiles/rubygems/index/latest_specs.4.8.gz.expected.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/m2r-2.1.0.gem (100%) rename {discovery => minecode}/tests/testfiles/rubygems/m2r-2.1.0.gem.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem (100%) rename {discovery => minecode}/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.mapped.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.metadata (100%) rename {discovery => minecode}/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem (100%) rename {discovery => minecode}/tests/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/ng-rails-csrf-0.1.0.gem (100%) rename {discovery => minecode}/tests/testfiles/rubygems/ng-rails-csrf-0.1.0.gem.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/small-0.2.gem (100%) rename {discovery => minecode}/tests/testfiles/rubygems/small-0.2.gem.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/small_wonder-0.1.10.gem (100%) rename {discovery => minecode}/tests/testfiles/rubygems/small_wonder-0.1.10.gem.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem (100%) rename {discovery => minecode}/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.mapped.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.package.json (100%) rename {discovery => minecode}/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.visited.json (100%) rename {discovery => minecode}/tests/testfiles/run_map/test_map_uri_does_update_with_same_mining_level-expected.json (100%) rename {discovery => minecode}/tests/testfiles/run_map/test_map_uri_replace_with_new_with_higher_new_mining_level-expected.json (100%) rename {discovery => minecode}/tests/testfiles/run_map/test_map_uri_update_only_empties_with_lesser_new_mining_level-expected.json (100%) rename {discovery => minecode}/tests/testfiles/run_map/test_merge_packages_no_replace-expected.json (100%) rename {discovery => minecode}/tests/testfiles/run_map/test_merge_packages_with_replace-expected.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/a-vitkus_profile.html (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/expected_heanet_rsync_dir.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/expected_netwiki.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/expected_sf_dir_index.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/expected_sf_dir_page.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/expected_sf_project.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/expected_sf_sitemap.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/expected_sf_sitemap_new.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/expected_sf_sitemap_page.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/expected_sf_sitemap_page_new.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/expected_sitemap-6.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/filezilla.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/mapper_niftyphp_expected.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/mapper_odanur_expected.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/mapper_omonoql_expected.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/mapper_openstunts_expected.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/monoql.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/netwiki.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/niftyphp.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/odanur.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/openstunts.json (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/rsync_heanet_sfnet.dir (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/sitemap-1.xml (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/sitemap-6.xml (100%) rename {discovery => minecode}/tests/testfiles/sourceforge/sitemap.xml (100%) rename {discovery => minecode}/utils.py (99%) rename {discovery => minecode}/utils_test.py (98%) rename {discovery => minecode}/utils_test.py.ABOUT (100%) rename {discovery => minecode}/utils_test.py.NOTICE (100%) rename {discovery => minecode}/version.py (100%) rename {discovery => minecode}/visitors/__init__.py (99%) rename {discovery => minecode}/visitors/debian.py (97%) rename {discovery => minecode}/visitors/fdroid.py (90%) rename {discovery => minecode}/visitors/freebsd.py (91%) rename {discovery => minecode}/visitors/java_stream.LICENSE (100%) rename {discovery => minecode}/visitors/java_stream.py (100%) rename {discovery => minecode}/visitors/java_stream.py.ABOUT (100%) rename {discovery => minecode}/visitors/maven.py (99%) rename {discovery => minecode}/visitors/npm.py (95%) rename {discovery => minecode}/visitors/pypi.py (95%) rename {discovery => minecode}/visitors/rubygems.py (95%) rename {discovery => minecode}/visitors/sourceforge.py (92%) diff --git a/clearcode/management/commands/clearload.py b/clearcode/management/commands/clearload.py index 8b6805f9..7f872dbb 100644 --- a/clearcode/management/commands/clearload.py +++ b/clearcode/management/commands/clearload.py @@ -8,7 +8,7 @@ # from clearcode.load import load -from discovery.management.commands import VerboseCommand +from minecode.management.commands import VerboseCommand class Command(VerboseCommand): diff --git a/clearcode/management/commands/clearsync.py b/clearcode/management/commands/clearsync.py index b7369297..5481d8f5 100644 --- a/clearcode/management/commands/clearsync.py +++ b/clearcode/management/commands/clearsync.py @@ -8,7 +8,7 @@ # from clearcode.sync import sync -from discovery.management.commands import VerboseCommand +from minecode.management.commands import VerboseCommand class Command(VerboseCommand): diff --git a/clearindex/harvest.py b/clearindex/harvest.py index eda9a664..0a7fdcf3 100644 --- a/clearindex/harvest.py +++ b/clearindex/harvest.py @@ -16,8 +16,8 @@ from packagedb.models import Package from packagedb.models import Resource -from discovery.management.commands.run_map import merge_packages -from discovery.utils import stringify_null_purl_fields +from minecode.management.commands.run_map import merge_packages +from minecode.utils import stringify_null_purl_fields logger = logging.getLogger(__name__) diff --git a/clearindex/management/commands/run_clearindex.py b/clearindex/management/commands/run_clearindex.py index a04118ce..8454299e 100644 --- a/clearindex/management/commands/run_clearindex.py +++ b/clearindex/management/commands/run_clearindex.py @@ -27,10 +27,10 @@ from clearcode.models import CDitem from clearindex import harvest -from discovery.management.commands import get_error_message -from discovery.management.commands import VerboseCommand -from discovery.management.commands.run_map import merge_packages -from discovery.utils import stringify_null_purl_fields +from minecode.management.commands import get_error_message +from minecode.management.commands import VerboseCommand +from minecode.management.commands.run_map import merge_packages +from minecode.utils import stringify_null_purl_fields from packagedb.models import Package diff --git a/clearindex/utils.py b/clearindex/utils.py index ef6b28c4..ebe0a5f2 100644 --- a/clearindex/utils.py +++ b/clearindex/utils.py @@ -17,7 +17,7 @@ from django.core.management.base import BaseCommand from django.test import TestCase as DjangoTestCase -from discovery.utils_test import JsonBasedTesting +from minecode.utils_test import JsonBasedTesting """ diff --git a/matchcode/management/commands/index_packages.py b/matchcode/management/commands/index_packages.py index e246d006..ebdd8d21 100644 --- a/matchcode/management/commands/index_packages.py +++ b/matchcode/management/commands/index_packages.py @@ -14,7 +14,7 @@ from django.db import transaction -from discovery.management.commands import VerboseCommand +from minecode.management.commands import VerboseCommand from matchcode.indexing import index_package_archives from matchcode.indexing import index_package_directories from matchcode.indexing import index_package_file diff --git a/matchcode/models.py b/matchcode/models.py index e5c51559..842f8dde 100644 --- a/matchcode/models.py +++ b/matchcode/models.py @@ -17,7 +17,7 @@ from django.forms.models import model_to_dict from django.utils.translation import gettext_lazy as _ -from discovery.management.commands import get_error_message +from minecode.management.commands import get_error_message from matchcode.fingerprinting import create_halohash_chunks from matchcode.fingerprinting import split_fingerprint from matchcode.halohash import byte_hamming_distance diff --git a/discovery/__init__.py b/minecode/__init__.py similarity index 88% rename from discovery/__init__.py rename to minecode/__init__.py index 9fbdcebb..8b9f47ea 100644 --- a/discovery/__init__.py +++ b/minecode/__init__.py @@ -10,10 +10,10 @@ import sys -from discovery import route +from minecode import route -default_app_config = 'discovery.apps.DiscoveryConfig' +default_app_config = 'minecode.apps.MinecodeConfig' sys_platform = str(sys.platform).lower() diff --git a/discovery/api.py b/minecode/api.py similarity index 94% rename from discovery/api.py rename to minecode/api.py index 1756a940..c6f2e92c 100644 --- a/discovery/api.py +++ b/minecode/api.py @@ -11,7 +11,7 @@ from rest_framework import serializers from rest_framework import viewsets -from discovery.models import ResourceURI +from minecode.models import ResourceURI class ResourceURISerializer(serializers.ModelSerializer): diff --git a/discovery/apps.py b/minecode/apps.py similarity index 82% rename from discovery/apps.py rename to minecode/apps.py index 8d2cfe29..478d1bee 100644 --- a/discovery/apps.py +++ b/minecode/apps.py @@ -12,6 +12,6 @@ from django.utils.translation import gettext_lazy as _ -class DiscoveryConfig(AppConfig): - name = 'discovery' - verbose_name = _('Discovery') +class MinecodeConfig(AppConfig): + name = 'minecode' + verbose_name = _('Minecode') diff --git a/discovery/bsd-new.LICENSE b/minecode/bsd-new.LICENSE similarity index 100% rename from discovery/bsd-new.LICENSE rename to minecode/bsd-new.LICENSE diff --git a/discovery/command.py b/minecode/command.py similarity index 98% rename from discovery/command.py rename to minecode/command.py index 1f4a6d6c..bf924e44 100644 --- a/discovery/command.py +++ b/minecode/command.py @@ -13,7 +13,7 @@ import signal import subprocess -from discovery import ON_WINDOWS +from minecode import ON_WINDOWS logger = logging.getLogger(__name__) diff --git a/discovery/debutils.py b/minecode/debutils.py similarity index 100% rename from discovery/debutils.py rename to minecode/debutils.py diff --git a/discovery/ls.py b/minecode/ls.py similarity index 100% rename from discovery/ls.py rename to minecode/ls.py diff --git a/discovery/management/__init__.py b/minecode/management/__init__.py similarity index 100% rename from discovery/management/__init__.py rename to minecode/management/__init__.py diff --git a/discovery/management/commands/__init__.py b/minecode/management/commands/__init__.py similarity index 100% rename from discovery/management/commands/__init__.py rename to minecode/management/commands/__init__.py diff --git a/discovery/management/commands/check_licenses.py b/minecode/management/commands/check_licenses.py similarity index 98% rename from discovery/management/commands/check_licenses.py rename to minecode/management/commands/check_licenses.py index 00c3049f..9fb0902c 100644 --- a/discovery/management/commands/check_licenses.py +++ b/minecode/management/commands/check_licenses.py @@ -20,7 +20,7 @@ from packagedb.models import Package -from discovery.management.commands import VerboseCommand +from minecode.management.commands import VerboseCommand """ Utility command to find license oddities. diff --git a/discovery/management/commands/check_uri.py b/minecode/management/commands/check_uri.py similarity index 94% rename from discovery/management/commands/check_uri.py rename to minecode/management/commands/check_uri.py index 7eebee2e..664fa3f7 100644 --- a/discovery/management/commands/check_uri.py +++ b/minecode/management/commands/check_uri.py @@ -16,12 +16,12 @@ # NOTE: mappers and visitors are Unused Import here: But importing the mappers # module triggers routes registration -from discovery import mappers # NOQA -from discovery import visitors # NOQA -from discovery import map_router -from discovery import visit_router -from discovery.models import ResourceURI -from discovery.route import NoRouteAvailable +from minecode import mappers # NOQA +from minecode import visitors # NOQA +from minecode import map_router +from minecode import visit_router +from minecode.models import ResourceURI +from minecode.route import NoRouteAvailable TRACE = False diff --git a/discovery/management/commands/dump_purls.py b/minecode/management/commands/dump_purls.py similarity index 100% rename from discovery/management/commands/dump_purls.py rename to minecode/management/commands/dump_purls.py diff --git a/discovery/management/commands/get_status.py b/minecode/management/commands/get_status.py similarity index 97% rename from discovery/management/commands/get_status.py rename to minecode/management/commands/get_status.py index a957d49e..9f24d12f 100644 --- a/discovery/management/commands/get_status.py +++ b/minecode/management/commands/get_status.py @@ -14,7 +14,7 @@ from django.core.management.base import BaseCommand -from discovery.models import ResourceURI +from minecode.models import ResourceURI from packagedb.models import Package logger = logging.getLogger(__name__) diff --git a/discovery/management/commands/remap.py b/minecode/management/commands/remap.py similarity index 97% rename from discovery/management/commands/remap.py rename to minecode/management/commands/remap.py index 8f84e297..97a08080 100644 --- a/discovery/management/commands/remap.py +++ b/minecode/management/commands/remap.py @@ -14,7 +14,7 @@ from django.core.management.base import BaseCommand from django.db.models import Q -from discovery.models import ResourceURI +from minecode.models import ResourceURI logger = logging.getLogger(__name__) logging.basicConfig(stream=sys.stdout) diff --git a/discovery/management/commands/run_map.py b/minecode/management/commands/run_map.py similarity index 98% rename from discovery/management/commands/run_map.py rename to minecode/management/commands/run_map.py index 0c2397af..08704e0f 100644 --- a/discovery/management/commands/run_map.py +++ b/minecode/management/commands/run_map.py @@ -25,18 +25,18 @@ # UnusedImport here! # But importing the mappers and visitors module triggers routes registration -from discovery import mappers # NOQA -from discovery import visitors # NOQA +from minecode import mappers # NOQA +from minecode import visitors # NOQA -from discovery import map_router -from discovery.models import ResourceURI +from minecode import map_router +from minecode.models import ResourceURI from packagedb.models import DependentPackage from packagedb.models import Package from packagedb.models import Party -from discovery.management.commands import get_error_message -from discovery.management.commands import VerboseCommand -from discovery.models import ScannableURI -from discovery.utils import stringify_null_purl_fields +from minecode.management.commands import get_error_message +from minecode.management.commands import VerboseCommand +from minecode.models import ScannableURI +from minecode.utils import stringify_null_purl_fields TRACE = True diff --git a/discovery/management/commands/run_visit.py b/minecode/management/commands/run_visit.py similarity index 97% rename from discovery/management/commands/run_visit.py rename to minecode/management/commands/run_visit.py index 54cd877d..b441ae1d 100644 --- a/discovery/management/commands/run_visit.py +++ b/minecode/management/commands/run_visit.py @@ -24,15 +24,15 @@ # UnusedImport here! # But importing the mappers and visitors module triggers routes registration -from discovery import mappers # NOQA -from discovery import visitors # NOQA -from discovery import visit_router +from minecode import mappers # NOQA +from minecode import visitors # NOQA +from minecode import visit_router -from discovery.management.commands import get_error_message -from discovery.management.commands import VerboseCommand +from minecode.management.commands import get_error_message +from minecode.management.commands import VerboseCommand -from discovery.models import ResourceURI -from discovery.route import NoRouteAvailable +from minecode.models import ResourceURI +from minecode.route import NoRouteAvailable logger = logging.getLogger(__name__) diff --git a/discovery/management/commands/seed.py b/minecode/management/commands/seed.py similarity index 93% rename from discovery/management/commands/seed.py rename to minecode/management/commands/seed.py index 4c3b18ae..48bfbb73 100644 --- a/discovery/management/commands/seed.py +++ b/minecode/management/commands/seed.py @@ -16,12 +16,12 @@ # UnusedImport here! # But importing the mappers and visitors module triggers routes registration -from discovery import mappers # NOQA -from discovery import visitors # NOQA +from minecode import mappers # NOQA +from minecode import visitors # NOQA -from discovery import seed -from discovery.models import ResourceURI -from discovery.management.commands import VerboseCommand +from minecode import seed +from minecode.models import ResourceURI +from minecode.management.commands import VerboseCommand logger = logging.getLogger(__name__) diff --git a/discovery/mappers/__init__.py b/minecode/mappers/__init__.py similarity index 100% rename from discovery/mappers/__init__.py rename to minecode/mappers/__init__.py diff --git a/discovery/mappers/debian.py b/minecode/mappers/debian.py similarity index 98% rename from discovery/mappers/debian.py rename to minecode/mappers/debian.py index 91eb35cd..10bcfa88 100644 --- a/discovery/mappers/debian.py +++ b/minecode/mappers/debian.py @@ -17,11 +17,11 @@ from packagedcode import models as scan_models from packageurl import PackageURL -from discovery import ls -from discovery import map_router -from discovery.mappers import Mapper -from discovery.utils import form_vcs_url -# from discovery import debutils +from minecode import ls +from minecode import map_router +from minecode.mappers import Mapper +from minecode.utils import form_vcs_url +# from minecode import debutils logger = logging.getLogger(__name__) @@ -385,7 +385,7 @@ def get_resourceuri_by_uri(uri): """ Return the Resource URI by searching with passing uri string value. """ - from discovery.models import ResourceURI + from minecode.models import ResourceURI uris = ResourceURI.objects.filter(uri=uri) if uris: return uris[0] diff --git a/discovery/mappers/fdroid.py b/minecode/mappers/fdroid.py similarity index 98% rename from discovery/mappers/fdroid.py rename to minecode/mappers/fdroid.py index 4b837f98..0460b118 100644 --- a/discovery/mappers/fdroid.py +++ b/minecode/mappers/fdroid.py @@ -12,8 +12,8 @@ from packagedcode.models import PackageData -from discovery import map_router -from discovery.mappers import Mapper +from minecode import map_router +from minecode.mappers import Mapper from packageurl import PackageURL from packagedcode.models import party_person from packagedcode.models import Party diff --git a/discovery/mappers/freebsd.py b/minecode/mappers/freebsd.py similarity index 94% rename from discovery/mappers/freebsd.py rename to minecode/mappers/freebsd.py index d462fe9d..cd3d89e9 100644 --- a/discovery/mappers/freebsd.py +++ b/minecode/mappers/freebsd.py @@ -14,9 +14,9 @@ from packagedcode.freebsd import CompactManifestHandler -from discovery import map_router -from discovery.mappers import Mapper -from discovery.utils import get_temp_dir +from minecode import map_router +from minecode.mappers import Mapper +from minecode.utils import get_temp_dir @map_router.route('https://pkg.freebsd.org/.*packagesite.txz') diff --git a/discovery/mappers/maven.py b/minecode/mappers/maven.py similarity index 96% rename from discovery/mappers/maven.py rename to minecode/mappers/maven.py index 10908b29..781cf218 100644 --- a/discovery/mappers/maven.py +++ b/minecode/mappers/maven.py @@ -16,10 +16,10 @@ from packagedcode.models import PackageData from packagedcode.maven import _parse -from discovery import map_router -from discovery.mappers import Mapper -from discovery.utils import parse_date -from discovery.visitors.maven import Artifact +from minecode import map_router +from minecode.mappers import Mapper +from minecode.utils import parse_date +from minecode.visitors.maven import Artifact TRACE = False diff --git a/discovery/mappers/npm.py b/minecode/mappers/npm.py similarity index 96% rename from discovery/mappers/npm.py rename to minecode/mappers/npm.py index 558a48d8..34d8f718 100644 --- a/discovery/mappers/npm.py +++ b/minecode/mappers/npm.py @@ -14,8 +14,8 @@ from packagedcode.npm import NpmPackageJsonHandler -from discovery import map_router -from discovery.mappers import Mapper +from minecode import map_router +from minecode.mappers import Mapper TRACE = False diff --git a/discovery/mappers/pypi.py b/minecode/mappers/pypi.py similarity index 98% rename from discovery/mappers/pypi.py rename to minecode/mappers/pypi.py index b708c980..fc423e1a 100644 --- a/discovery/mappers/pypi.py +++ b/minecode/mappers/pypi.py @@ -12,9 +12,9 @@ from packagedcode import models as scan_models -from discovery import map_router -from discovery.mappers import Mapper -from discovery.utils import parse_date +from minecode import map_router +from minecode.mappers import Mapper +from minecode.utils import parse_date @map_router.route('https://pypi.python.org/pypi/[^/]+/[^/]+/json') diff --git a/discovery/mappers/rubygems.py b/minecode/mappers/rubygems.py similarity index 98% rename from discovery/mappers/rubygems.py rename to minecode/mappers/rubygems.py index 45c9d437..9ae0513b 100644 --- a/discovery/mappers/rubygems.py +++ b/minecode/mappers/rubygems.py @@ -15,10 +15,10 @@ from packagedcode.models import DependentPackage from packagedcode.models import PackageData -from discovery import map_router -from discovery import saneyaml -from discovery.mappers import Mapper -from discovery.utils import parse_date +from minecode import map_router +from minecode import saneyaml +from minecode.mappers import Mapper +from minecode.utils import parse_date logger = logging.getLogger(__name__) handler = logging.StreamHandler() diff --git a/discovery/mappers/sourceforge.py b/minecode/mappers/sourceforge.py similarity index 98% rename from discovery/mappers/sourceforge.py rename to minecode/mappers/sourceforge.py index baf96f9a..77880176 100644 --- a/discovery/mappers/sourceforge.py +++ b/minecode/mappers/sourceforge.py @@ -11,8 +11,8 @@ from packagedcode import models as scan_models -from discovery import map_router -from discovery.mappers import Mapper +from minecode import map_router +from minecode.mappers import Mapper @map_router.route('https?://sourceforge.net/api/project/name/[a-z0-9.-]+/json', diff --git a/discovery/mappings/__init__.py b/minecode/mappings/__init__.py similarity index 100% rename from discovery/mappings/__init__.py rename to minecode/mappings/__init__.py diff --git a/discovery/mappings/pypi_trove.py b/minecode/mappings/pypi_trove.py similarity index 100% rename from discovery/mappings/pypi_trove.py rename to minecode/mappings/pypi_trove.py diff --git a/discovery/migrations/0001_initial.py b/minecode/migrations/0001_initial.py similarity index 100% rename from discovery/migrations/0001_initial.py rename to minecode/migrations/0001_initial.py diff --git a/discovery/migrations/0002_auto_20160707_1249.py b/minecode/migrations/0002_auto_20160707_1249.py similarity index 99% rename from discovery/migrations/0002_auto_20160707_1249.py rename to minecode/migrations/0002_auto_20160707_1249.py index b64a51a8..eae0b342 100644 --- a/discovery/migrations/0002_auto_20160707_1249.py +++ b/minecode/migrations/0002_auto_20160707_1249.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0001_initial'), + ('minecode', '0001_initial'), ] operations = [ diff --git a/discovery/migrations/0003_auto_20160712_1223.py b/minecode/migrations/0003_auto_20160712_1223.py similarity index 95% rename from discovery/migrations/0003_auto_20160712_1223.py rename to minecode/migrations/0003_auto_20160712_1223.py index 5de60e68..3d1e69bf 100644 --- a/discovery/migrations/0003_auto_20160712_1223.py +++ b/minecode/migrations/0003_auto_20160712_1223.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0002_auto_20160707_1249'), + ('minecode', '0002_auto_20160707_1249'), ] operations = [ diff --git a/discovery/migrations/0004_auto_20160713_1731.py b/minecode/migrations/0004_auto_20160713_1731.py similarity index 90% rename from discovery/migrations/0004_auto_20160713_1731.py rename to minecode/migrations/0004_auto_20160713_1731.py index c2da43fe..a3c8fa9b 100644 --- a/discovery/migrations/0004_auto_20160713_1731.py +++ b/minecode/migrations/0004_auto_20160713_1731.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0003_auto_20160712_1223'), + ('minecode', '0003_auto_20160712_1223'), ] operations = [ diff --git a/discovery/migrations/0005_resourceuri_metadata.py b/minecode/migrations/0005_resourceuri_metadata.py similarity index 92% rename from discovery/migrations/0005_resourceuri_metadata.py rename to minecode/migrations/0005_resourceuri_metadata.py index 4f796f6f..b32f1bd3 100644 --- a/discovery/migrations/0005_resourceuri_metadata.py +++ b/minecode/migrations/0005_resourceuri_metadata.py @@ -9,7 +9,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0004_auto_20160713_1731'), + ('minecode', '0004_auto_20160713_1731'), ] operations = [ diff --git a/discovery/migrations/0006_remove_resourceuri_metadata.py b/minecode/migrations/0006_remove_resourceuri_metadata.py similarity index 87% rename from discovery/migrations/0006_remove_resourceuri_metadata.py rename to minecode/migrations/0006_remove_resourceuri_metadata.py index 6b028def..84b74e3c 100644 --- a/discovery/migrations/0006_remove_resourceuri_metadata.py +++ b/minecode/migrations/0006_remove_resourceuri_metadata.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0005_resourceuri_metadata'), + ('minecode', '0005_resourceuri_metadata'), ] operations = [ diff --git a/discovery/migrations/0007_change_unique_constraints_on_resourceuri.py b/minecode/migrations/0007_change_unique_constraints_on_resourceuri.py similarity index 92% rename from discovery/migrations/0007_change_unique_constraints_on_resourceuri.py rename to minecode/migrations/0007_change_unique_constraints_on_resourceuri.py index 919f189e..3569334d 100644 --- a/discovery/migrations/0007_change_unique_constraints_on_resourceuri.py +++ b/minecode/migrations/0007_change_unique_constraints_on_resourceuri.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0006_remove_resourceuri_metadata'), + ('minecode', '0006_remove_resourceuri_metadata'), ] operations = [ diff --git a/discovery/migrations/0008_change_default_sort_order_on_resourceuri.py b/minecode/migrations/0008_change_default_sort_order_on_resourceuri.py similarity index 85% rename from discovery/migrations/0008_change_default_sort_order_on_resourceuri.py rename to minecode/migrations/0008_change_default_sort_order_on_resourceuri.py index a0ab7ff7..521694e6 100644 --- a/discovery/migrations/0008_change_default_sort_order_on_resourceuri.py +++ b/minecode/migrations/0008_change_default_sort_order_on_resourceuri.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0007_change_unique_constraints_on_resourceuri'), + ('minecode', '0007_change_unique_constraints_on_resourceuri'), ] operations = [ diff --git a/discovery/migrations/0009_resourceuri_source_uri.py b/minecode/migrations/0009_resourceuri_source_uri.py similarity index 91% rename from discovery/migrations/0009_resourceuri_source_uri.py rename to minecode/migrations/0009_resourceuri_source_uri.py index 387811d6..c3bda1ce 100644 --- a/discovery/migrations/0009_resourceuri_source_uri.py +++ b/minecode/migrations/0009_resourceuri_source_uri.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0008_change_default_sort_order_on_resourceuri'), + ('minecode', '0008_change_default_sort_order_on_resourceuri'), ] operations = [ diff --git a/discovery/migrations/0010_resourceuri_mining_level.py b/minecode/migrations/0010_resourceuri_mining_level.py similarity index 91% rename from discovery/migrations/0010_resourceuri_mining_level.py rename to minecode/migrations/0010_resourceuri_mining_level.py index a45db70c..2a6d535a 100644 --- a/discovery/migrations/0010_resourceuri_mining_level.py +++ b/minecode/migrations/0010_resourceuri_mining_level.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0009_resourceuri_source_uri'), + ('minecode', '0009_resourceuri_source_uri'), ] operations = [ diff --git a/discovery/migrations/0011_auto_20170807_0253.py b/minecode/migrations/0011_auto_20170807_0253.py similarity index 95% rename from discovery/migrations/0011_auto_20170807_0253.py rename to minecode/migrations/0011_auto_20170807_0253.py index 2358f4c5..489f6e0c 100644 --- a/discovery/migrations/0011_auto_20170807_0253.py +++ b/minecode/migrations/0011_auto_20170807_0253.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0010_resourceuri_mining_level'), + ('minecode', '0010_resourceuri_mining_level'), ] operations = [ diff --git a/discovery/migrations/0012_auto_20170807_0444.py b/minecode/migrations/0012_auto_20170807_0444.py similarity index 88% rename from discovery/migrations/0012_auto_20170807_0444.py rename to minecode/migrations/0012_auto_20170807_0444.py index c4fb1080..0fd6175d 100644 --- a/discovery/migrations/0012_auto_20170807_0444.py +++ b/minecode/migrations/0012_auto_20170807_0444.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0011_auto_20170807_0253'), + ('minecode', '0011_auto_20170807_0253'), ] operations = [ diff --git a/discovery/migrations/0013_auto_20170807_0511.py b/minecode/migrations/0013_auto_20170807_0511.py similarity index 72% rename from discovery/migrations/0013_auto_20170807_0511.py rename to minecode/migrations/0013_auto_20170807_0511.py index 2c31a5ef..e1694bc5 100644 --- a/discovery/migrations/0013_auto_20170807_0511.py +++ b/minecode/migrations/0013_auto_20170807_0511.py @@ -8,16 +8,16 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0012_auto_20170807_0444'), + ('minecode', '0012_auto_20170807_0444'), ] operations = [ migrations.AddIndex( model_name='resourceuri', - index=models.Index(fields=['is_visitable', 'last_visit_date', 'wip_date'], name='discovery_r_is_visi_29fde2_idx'), + index=models.Index(fields=['is_visitable', 'last_visit_date', 'wip_date'], name='minecode_r_is_visi_29fde2_idx'), ), migrations.AddIndex( model_name='resourceuri', - index=models.Index(fields=['is_mappable', 'last_visit_date', 'wip_date', 'last_map_date', 'visit_error'], name='discovery_r_is_mapp_e362dc_idx'), + index=models.Index(fields=['is_mappable', 'last_visit_date', 'wip_date', 'last_map_date', 'visit_error'], name='minecode_r_is_mapp_e362dc_idx'), ), ] diff --git a/discovery/migrations/0014_auto_20170807_0529.py b/minecode/migrations/0014_auto_20170807_0529.py similarity index 81% rename from discovery/migrations/0014_auto_20170807_0529.py rename to minecode/migrations/0014_auto_20170807_0529.py index ba4130f2..b72923cc 100644 --- a/discovery/migrations/0014_auto_20170807_0529.py +++ b/minecode/migrations/0014_auto_20170807_0529.py @@ -8,12 +8,12 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0013_auto_20170807_0511'), + ('minecode', '0013_auto_20170807_0511'), ] operations = [ migrations.AddIndex( model_name='resourceuri', - index=models.Index(fields=['-priority', 'rank'], name='discovery_r_priorit_daf7b6_idx'), + index=models.Index(fields=['-priority', 'rank'], name='minecode_r_priorit_daf7b6_idx'), ), ] diff --git a/discovery/migrations/0015_auto_20180607_0851.py b/minecode/migrations/0015_auto_20180607_0851.py similarity index 92% rename from discovery/migrations/0015_auto_20180607_0851.py rename to minecode/migrations/0015_auto_20180607_0851.py index 14be2039..72a43a11 100644 --- a/discovery/migrations/0015_auto_20180607_0851.py +++ b/minecode/migrations/0015_auto_20180607_0851.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0014_auto_20170807_0529'), + ('minecode', '0014_auto_20170807_0529'), ] operations = [ diff --git a/discovery/migrations/0016_remove_resourceuri_sig.py b/minecode/migrations/0016_remove_resourceuri_sig.py similarity index 87% rename from discovery/migrations/0016_remove_resourceuri_sig.py rename to minecode/migrations/0016_remove_resourceuri_sig.py index c2039720..23dd20f0 100644 --- a/discovery/migrations/0016_remove_resourceuri_sig.py +++ b/minecode/migrations/0016_remove_resourceuri_sig.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0015_auto_20180607_0851'), + ('minecode', '0015_auto_20180607_0851'), ] operations = [ diff --git a/discovery/migrations/0017_auto_20180619_2236.py b/minecode/migrations/0017_auto_20180619_2236.py similarity index 96% rename from discovery/migrations/0017_auto_20180619_2236.py rename to minecode/migrations/0017_auto_20180619_2236.py index 282443e9..9f4a2ae5 100644 --- a/discovery/migrations/0017_auto_20180619_2236.py +++ b/minecode/migrations/0017_auto_20180619_2236.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0016_remove_resourceuri_sig'), + ('minecode', '0016_remove_resourceuri_sig'), ] operations = [ @@ -41,11 +41,11 @@ class Migration(migrations.Migration): ), migrations.AddIndex( model_name='scannableuri', - index=models.Index(fields=['scan_status', 'scan_request_date', 'last_status_poll_date'], name='discovery_s_scan_st_21b64d_idx'), + index=models.Index(fields=['scan_status', 'scan_request_date', 'last_status_poll_date'], name='minecode_s_scan_st_21b64d_idx'), ), migrations.AddIndex( model_name='scannableuri', - index=models.Index(fields=['-priority', 'rank'], name='discovery_s_priorit_5b5e01_idx'), + index=models.Index(fields=['-priority', 'rank'], name='minecode_s_priorit_5b5e01_idx'), ), migrations.AlterUniqueTogether( name='scannableuri', diff --git a/discovery/migrations/0018_scannableuri_package_id.py b/minecode/migrations/0018_scannableuri_package_id.py similarity index 90% rename from discovery/migrations/0018_scannableuri_package_id.py rename to minecode/migrations/0018_scannableuri_package_id.py index a2c440d0..4aa4f382 100644 --- a/discovery/migrations/0018_scannableuri_package_id.py +++ b/minecode/migrations/0018_scannableuri_package_id.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0017_auto_20180619_2236'), + ('minecode', '0017_auto_20180619_2236'), ] operations = [ diff --git a/discovery/migrations/0019_auto_20180716_1648.py b/minecode/migrations/0019_auto_20180716_1648.py similarity index 91% rename from discovery/migrations/0019_auto_20180716_1648.py rename to minecode/migrations/0019_auto_20180716_1648.py index 9254f89e..18484a98 100644 --- a/discovery/migrations/0019_auto_20180716_1648.py +++ b/minecode/migrations/0019_auto_20180716_1648.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0018_scannableuri_package_id'), + ('minecode', '0018_scannableuri_package_id'), ] operations = [ diff --git a/discovery/migrations/0020_resourceuri_package_url.py b/minecode/migrations/0020_resourceuri_package_url.py similarity index 91% rename from discovery/migrations/0020_resourceuri_package_url.py rename to minecode/migrations/0020_resourceuri_package_url.py index 247e1638..5a5776da 100644 --- a/discovery/migrations/0020_resourceuri_package_url.py +++ b/minecode/migrations/0020_resourceuri_package_url.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0019_auto_20180716_1648'), + ('minecode', '0019_auto_20180716_1648'), ] operations = [ diff --git a/discovery/migrations/0021_auto_20181026_1635.py b/minecode/migrations/0021_auto_20181026_1635.py similarity index 93% rename from discovery/migrations/0021_auto_20181026_1635.py rename to minecode/migrations/0021_auto_20181026_1635.py index 8bd9d285..abf64f54 100644 --- a/discovery/migrations/0021_auto_20181026_1635.py +++ b/minecode/migrations/0021_auto_20181026_1635.py @@ -10,7 +10,7 @@ class Migration(migrations.Migration): dependencies = [ ('packagedb', '0023_package_source_packages'), - ('discovery', '0020_resourceuri_package_url'), + ('minecode', '0020_resourceuri_package_url'), ] operations = [ diff --git a/discovery/migrations/0022_auto_20190307_2332.py b/minecode/migrations/0022_auto_20190307_2332.py similarity index 69% rename from discovery/migrations/0022_auto_20190307_2332.py rename to minecode/migrations/0022_auto_20190307_2332.py index 22082d4a..f588b823 100644 --- a/discovery/migrations/0022_auto_20190307_2332.py +++ b/minecode/migrations/0022_auto_20190307_2332.py @@ -8,17 +8,17 @@ class Migration(migrations.Migration): dependencies = [ - ('discovery', '0021_auto_20181026_1635'), + ('minecode', '0021_auto_20181026_1635'), ] operations = [ migrations.RemoveIndex( model_name='scannableuri', - name='discovery_s_priorit_5b5e01_idx', + name='minecode_s_priorit_5b5e01_idx', ), migrations.RemoveIndex( model_name='resourceuri', - name='discovery_r_priorit_daf7b6_idx', + name='minecode_r_priorit_daf7b6_idx', ), migrations.RemoveField( model_name='resourceuri', @@ -30,10 +30,10 @@ class Migration(migrations.Migration): ), migrations.AddIndex( model_name='scannableuri', - index=models.Index(fields=['-priority'], name='discovery_s_priorit_2f397f_idx'), + index=models.Index(fields=['-priority'], name='minecode_s_priorit_2f397f_idx'), ), migrations.AddIndex( model_name='resourceuri', - index=models.Index(fields=['-priority'], name='discovery_r_priorit_b8bce2_idx'), + index=models.Index(fields=['-priority'], name='minecode_r_priorit_b8bce2_idx'), ), ] diff --git a/discovery/migrations/__init__.py b/minecode/migrations/__init__.py similarity index 100% rename from discovery/migrations/__init__.py rename to minecode/migrations/__init__.py diff --git a/discovery/models.py b/minecode/models.py similarity index 99% rename from discovery/models.py rename to minecode/models.py index f25fda5f..718dcb40 100644 --- a/discovery/models.py +++ b/minecode/models.py @@ -16,13 +16,13 @@ from django.utils import timezone -from discovery import map_router -from discovery import visit_router +from minecode import map_router +from minecode import visit_router # UnusedImport here! # But importing the mappers and visitors module triggers routes registration -from discovery import mappers # NOQA -from discovery import visitors # NOQA +from minecode import mappers # NOQA +from minecode import visitors # NOQA from packagedb.models import Package diff --git a/discovery/route.py b/minecode/route.py similarity index 100% rename from discovery/route.py rename to minecode/route.py diff --git a/discovery/rsync.py b/minecode/rsync.py similarity index 98% rename from discovery/rsync.py rename to minecode/rsync.py index c8b7016b..1e0971db 100644 --- a/discovery/rsync.py +++ b/minecode/rsync.py @@ -15,8 +15,8 @@ import arrow from dateutil import tz -from discovery import command -from discovery.utils import get_temp_file +from minecode import command +from minecode.utils import get_temp_file logger = logging.getLogger(__name__) # import sys diff --git a/discovery/saneyaml.py b/minecode/saneyaml.py similarity index 100% rename from discovery/saneyaml.py rename to minecode/saneyaml.py diff --git a/discovery/seed.py b/minecode/seed.py similarity index 97% rename from discovery/seed.py rename to minecode/seed.py index 0ba75823..8baccc2a 100644 --- a/discovery/seed.py +++ b/minecode/seed.py @@ -61,6 +61,6 @@ def get_configured_seeders(): Return Seeder class qualified names referenced as active in the settings or environment. """ - from discovery.management.commands import get_settings + from minecode.management.commands import get_settings # ACTIVE_VISITOR_SEEDS is a list of fully qualified Seeder subclass strings return get_settings('ACTIVE_SEEDERS') or [] diff --git a/discovery/tests/__init__.py b/minecode/tests/__init__.py similarity index 100% rename from discovery/tests/__init__.py rename to minecode/tests/__init__.py diff --git a/discovery/tests/test_command.py b/minecode/tests/test_command.py similarity index 89% rename from discovery/tests/test_command.py rename to minecode/tests/test_command.py index dab6ce22..a21f8d9e 100644 --- a/discovery/tests/test_command.py +++ b/minecode/tests/test_command.py @@ -10,9 +10,9 @@ import os -from discovery import command -from discovery import ON_WINDOWS -from discovery.utils_test import MiningTestCase +from minecode import command +from minecode import ON_WINDOWS +from minecode.utils_test import MiningTestCase class CommandTest(MiningTestCase): diff --git a/discovery/tests/test_debian.py b/minecode/tests/test_debian.py similarity index 98% rename from discovery/tests/test_debian.py rename to minecode/tests/test_debian.py index 73202033..54c3a117 100644 --- a/discovery/tests/test_debian.py +++ b/minecode/tests/test_debian.py @@ -16,12 +16,12 @@ from mock import patch from debian_inspector import debcon -from discovery.utils_test import mocked_requests_get -from discovery.utils_test import JsonBasedTesting +from minecode.utils_test import mocked_requests_get +from minecode.utils_test import JsonBasedTesting -from discovery import debutils -from discovery.mappers import debian as debian_mapper -from discovery.visitors import debian as debian_visitor +from minecode import debutils +from minecode.mappers import debian as debian_mapper +from minecode.visitors import debian as debian_visitor class BaseDebianTest(JsonBasedTesting): diff --git a/discovery/tests/test_fdroid.py b/minecode/tests/test_fdroid.py similarity index 88% rename from discovery/tests/test_fdroid.py rename to minecode/tests/test_fdroid.py index 058fe61c..8a09509c 100644 --- a/discovery/tests/test_fdroid.py +++ b/minecode/tests/test_fdroid.py @@ -12,12 +12,12 @@ from mock import patch -from discovery.utils_test import mocked_requests_get -from discovery.utils_test import JsonBasedTesting +from minecode.utils_test import mocked_requests_get +from minecode.utils_test import JsonBasedTesting -from discovery.mappers import fdroid as fdroid_mapper -from discovery.visitors import fdroid as fdroid_visitor -from discovery.visitors import URI +from minecode.mappers import fdroid as fdroid_mapper +from minecode.visitors import fdroid as fdroid_visitor +from minecode.visitors import URI class TestFdroidVisitor(JsonBasedTesting): diff --git a/discovery/tests/test_freebsd.py b/minecode/tests/test_freebsd.py similarity index 93% rename from discovery/tests/test_freebsd.py rename to minecode/tests/test_freebsd.py index 9f7028ce..27fdda28 100644 --- a/discovery/tests/test_freebsd.py +++ b/minecode/tests/test_freebsd.py @@ -14,11 +14,11 @@ from mock import Mock from mock import patch -from discovery.utils_test import mocked_requests_get -from discovery.utils_test import JsonBasedTesting +from minecode.utils_test import mocked_requests_get +from minecode.utils_test import JsonBasedTesting -from discovery import mappers -from discovery.visitors import freebsd +from minecode import mappers +from minecode.visitors import freebsd class FreeBSDVistorTest(JsonBasedTesting): diff --git a/discovery/tests/test_housekeeping.py b/minecode/tests/test_housekeeping.py similarity index 93% rename from discovery/tests/test_housekeeping.py rename to minecode/tests/test_housekeeping.py index 879fc081..4f92361b 100644 --- a/discovery/tests/test_housekeeping.py +++ b/minecode/tests/test_housekeeping.py @@ -19,14 +19,14 @@ import packagedb -from discovery.utils_test import mocked_requests_get -from discovery.utils_test import JsonBasedTesting +from minecode.utils_test import mocked_requests_get +from minecode.utils_test import JsonBasedTesting -from discovery.management.commands.check_licenses import find_ambiguous_packages -from discovery.management.commands.run_map import map_uri -from discovery.management.commands.run_visit import visit_uri +from minecode.management.commands.check_licenses import find_ambiguous_packages +from minecode.management.commands.run_map import map_uri +from minecode.management.commands.run_visit import visit_uri -from discovery.models import ResourceURI +from minecode.models import ResourceURI class PackageLicenseCheckTest(JsonBasedTesting, DjangoTestCase): diff --git a/discovery/tests/test_ls.py b/minecode/tests/test_ls.py similarity index 97% rename from discovery/tests/test_ls.py rename to minecode/tests/test_ls.py index 50ae4633..79d43d5d 100644 --- a/discovery/tests/test_ls.py +++ b/minecode/tests/test_ls.py @@ -12,8 +12,8 @@ import os -from discovery.utils_test import JsonBasedTesting -from discovery import ls +from minecode.utils_test import JsonBasedTesting +from minecode import ls class ParseDirectoryListingTest(JsonBasedTesting): diff --git a/discovery/tests/test_maven.py b/minecode/tests/test_maven.py similarity index 98% rename from discovery/tests/test_maven.py rename to minecode/tests/test_maven.py index 29a5b927..a0367dd7 100644 --- a/discovery/tests/test_maven.py +++ b/minecode/tests/test_maven.py @@ -19,16 +19,16 @@ import packagedb -from discovery.utils_test import mocked_requests_get -from discovery.utils_test import JsonBasedTesting -from discovery.utils_test import model_to_dict +from minecode.utils_test import mocked_requests_get +from minecode.utils_test import JsonBasedTesting +from minecode.utils_test import model_to_dict -from discovery.management.commands.run_map import map_uri -from discovery.management.commands.run_visit import visit_uri +from minecode.management.commands.run_map import map_uri +from minecode.management.commands.run_visit import visit_uri -from discovery.mappers import maven as maven_mapper -from discovery.models import ResourceURI -from discovery.visitors import maven as maven_visitor +from minecode.mappers import maven as maven_mapper +from minecode.models import ResourceURI +from minecode.visitors import maven as maven_visitor # TODO: add tests from /maven-indexer/indexer-core/src/test/java/org/acche/maven/index/artifact diff --git a/discovery/tests/test_models.py b/minecode/tests/test_models.py similarity index 99% rename from discovery/tests/test_models.py rename to minecode/tests/test_models.py index ddce0896..f3ab4ef5 100644 --- a/discovery/tests/test_models.py +++ b/minecode/tests/test_models.py @@ -13,13 +13,13 @@ from django.test import TestCase from django.utils import timezone -from discovery import visitors -from discovery import mappers +from minecode import visitors +from minecode import mappers -from discovery.models import ResourceURI +from minecode.models import ResourceURI from packagedb.models import Package -from discovery.models import get_canonical -from discovery.models import ScannableURI +from minecode.models import get_canonical +from minecode.models import ScannableURI class ResourceURIModelTestCase(TestCase): diff --git a/discovery/tests/test_npm.py b/minecode/tests/test_npm.py similarity index 97% rename from discovery/tests/test_npm.py rename to minecode/tests/test_npm.py index d51d0576..34c56b12 100644 --- a/discovery/tests/test_npm.py +++ b/minecode/tests/test_npm.py @@ -16,13 +16,13 @@ from mock import Mock from mock import patch -from discovery.utils_test import mocked_requests_get -from discovery.utils_test import JsonBasedTesting +from minecode.utils_test import mocked_requests_get +from minecode.utils_test import JsonBasedTesting -from discovery import mappers -from discovery import route -from discovery.models import ResourceURI -from discovery.visitors import npm +from minecode import mappers +from minecode import route +from minecode.models import ResourceURI +from minecode.visitors import npm class TestNPMVisit(JsonBasedTesting): diff --git a/discovery/tests/test_pypi.py b/minecode/tests/test_pypi.py similarity index 96% rename from discovery/tests/test_pypi.py rename to minecode/tests/test_pypi.py index ef6502a9..f5eb4396 100644 --- a/discovery/tests/test_pypi.py +++ b/minecode/tests/test_pypi.py @@ -19,15 +19,15 @@ from packagedb.models import Package -from discovery.utils_test import mocked_requests_get -from discovery.utils_test import JsonBasedTesting - -from discovery import mappers -from discovery import visitors -from discovery.visitors import URI -from discovery.models import ResourceURI -from discovery.route import Router -from discovery.management.commands.run_map import map_uri +from minecode.utils_test import mocked_requests_get +from minecode.utils_test import JsonBasedTesting + +from minecode import mappers +from minecode import visitors +from minecode.visitors import URI +from minecode.models import ResourceURI +from minecode.route import Router +from minecode.management.commands.run_map import map_uri class TestPypiVisit(JsonBasedTesting, DjangoTestCase): diff --git a/discovery/tests/test_route.py b/minecode/tests/test_route.py similarity index 99% rename from discovery/tests/test_route.py rename to minecode/tests/test_route.py index 56cfe3a2..0ff2660b 100644 --- a/discovery/tests/test_route.py +++ b/minecode/tests/test_route.py @@ -10,8 +10,8 @@ from django.test import TestCase -from discovery import route -from discovery.route import Rule +from minecode import route +from minecode.route import Rule class RouteTest(TestCase): diff --git a/discovery/tests/test_rsync.py b/minecode/tests/test_rsync.py similarity index 98% rename from discovery/tests/test_rsync.py rename to minecode/tests/test_rsync.py index e591d48c..21a72472 100644 --- a/discovery/tests/test_rsync.py +++ b/minecode/tests/test_rsync.py @@ -7,13 +7,13 @@ # See https://aboutcode.org for more information about nexB OSS projects. # -# +# from unittest import skipIf import os -from discovery import rsync -from discovery import ON_WINDOWS -from discovery.utils_test import MiningTestCase +from minecode import rsync +from minecode import ON_WINDOWS +from minecode.utils_test import MiningTestCase class RsyncTest(MiningTestCase): diff --git a/discovery/tests/test_rubygems.py b/minecode/tests/test_rubygems.py similarity index 92% rename from discovery/tests/test_rubygems.py rename to minecode/tests/test_rubygems.py index 7230b852..3989a284 100644 --- a/discovery/tests/test_rubygems.py +++ b/minecode/tests/test_rubygems.py @@ -20,23 +20,23 @@ from commoncode.fileutils import file_name from django.test import TestCase as DjangoTestCase -from discovery.utils_test import mocked_requests_get -from discovery.utils_test import JsonBasedTesting -from discovery.utils_test import model_to_dict +from minecode.utils_test import mocked_requests_get +from minecode.utils_test import JsonBasedTesting +from minecode.utils_test import model_to_dict -from discovery import mappers -from discovery import route -from discovery.models import ResourceURI -from discovery import visit_router -from discovery.mappers.rubygems import build_rubygem_packages_from_api_data -from discovery.mappers.rubygems import build_rubygem_packages_from_metadata -from discovery.mappers.rubygems import RubyGemsApiVersionsJsonMapper -from discovery.mappers.rubygems import RubyGemsPackageArchiveMetadataMapper +from minecode import mappers +from minecode import route +from minecode.models import ResourceURI +from minecode import visit_router +from minecode.mappers.rubygems import build_rubygem_packages_from_api_data +from minecode.mappers.rubygems import build_rubygem_packages_from_metadata +from minecode.mappers.rubygems import RubyGemsApiVersionsJsonMapper +from minecode.mappers.rubygems import RubyGemsPackageArchiveMetadataMapper -from discovery.visitors.rubygems import get_gem_metadata -from discovery.visitors.rubygems import RubyGemsApiManyVersionsVisitor -from discovery.visitors.rubygems import RubyGemsIndexVisitor -from discovery.visitors.rubygems import RubyGemsPackageArchiveMetadataVisitor +from minecode.visitors.rubygems import get_gem_metadata +from minecode.visitors.rubygems import RubyGemsApiManyVersionsVisitor +from minecode.visitors.rubygems import RubyGemsIndexVisitor +from minecode.visitors.rubygems import RubyGemsPackageArchiveMetadataVisitor # @@ -265,8 +265,8 @@ class RubyEnd2EndTest(JsonBasedTesting, DjangoTestCase): test_data_dir = os.path.join(os.path.dirname(__file__), 'testfiles') def test_visit_and_map_end2end(self): - from discovery.management.commands.run_visit import visit_uri - from discovery.management.commands.run_map import map_uri + from minecode.management.commands.run_visit import visit_uri + from minecode.management.commands.run_map import map_uri import packagedb uri = 'https://rubygems.org/downloads/sprockets-vendor_gems-0.1.3.gem' diff --git a/discovery/tests/test_run_map.py b/minecode/tests/test_run_map.py similarity index 98% rename from discovery/tests/test_run_map.py rename to minecode/tests/test_run_map.py index acf56369..ed250cba 100644 --- a/discovery/tests/test_run_map.py +++ b/minecode/tests/test_run_map.py @@ -18,14 +18,14 @@ import packagedb -from discovery.utils_test import MiningTestCase - -from discovery.management.commands.run_map import map_uri -from discovery.management.commands.run_map import merge_packages -from discovery.models import ResourceURI -from discovery.route import Router -from discovery.models import ScannableURI -from discovery.utils_test import JsonBasedTesting +from minecode.utils_test import MiningTestCase + +from minecode.management.commands.run_map import map_uri +from minecode.management.commands.run_map import merge_packages +from minecode.models import ResourceURI +from minecode.route import Router +from minecode.models import ScannableURI +from minecode.utils_test import JsonBasedTesting class RunMapTest(JsonBasedTesting, MiningTestCase): diff --git a/discovery/tests/test_run_visit.py b/minecode/tests/test_run_visit.py similarity index 98% rename from discovery/tests/test_run_visit.py rename to minecode/tests/test_run_visit.py index e53a1d02..9ff51287 100644 --- a/discovery/tests/test_run_visit.py +++ b/minecode/tests/test_run_visit.py @@ -15,11 +15,11 @@ from django.core import management from django.forms.models import model_to_dict -from discovery.utils_test import MiningTestCase -from discovery.management.commands.run_visit import visit_uri -from discovery.models import ResourceURI -from discovery.route import Router -from discovery.visitors import URI +from minecode.utils_test import MiningTestCase +from minecode.management.commands.run_visit import visit_uri +from minecode.models import ResourceURI +from minecode.route import Router +from minecode.visitors import URI class RunVisitWithCounterTest(MiningTestCase): diff --git a/discovery/tests/test_seed.py b/minecode/tests/test_seed.py similarity index 95% rename from discovery/tests/test_seed.py rename to minecode/tests/test_seed.py index 46c87d6c..7a59b0f4 100644 --- a/discovery/tests/test_seed.py +++ b/minecode/tests/test_seed.py @@ -16,11 +16,11 @@ from django.utils import timezone from mock import patch -from discovery.management.commands.seed import SEED_PRIORITY -from discovery.management.commands.seed import insert_seed_uris -from discovery.models import ResourceURI -from discovery import seed -from discovery.utils_test import MiningTestCase +from minecode.management.commands.seed import SEED_PRIORITY +from minecode.management.commands.seed import insert_seed_uris +from minecode.models import ResourceURI +from minecode import seed +from minecode.utils_test import MiningTestCase class RevisitSeedTest(MiningTestCase): @@ -137,7 +137,7 @@ def get_seeds(self): self.SampleSeed3 = SampleSeed3() self.SampleSeed4 = SampleSeed4() - @patch('discovery.seed.get_active_seeders') + @patch('minecode.seed.get_active_seeders') def test_seed_command(self, mock_get_active_seeders): output = StringIO() mock_get_active_seeders.return_value = [self.SampleSeed0] @@ -162,7 +162,7 @@ def test_seed_command(self, mock_get_active_seeders): self.assertEqual(3, len([s.is_visitable for s in seeded])) self.assertTrue(all(s.priority == SEED_PRIORITY for s in seeded)) - @patch('discovery.seed.get_active_seeders') + @patch('minecode.seed.get_active_seeders') def test_insert_seed_uris_inserts_uris_for_active_seeders_with_pattern(self, mock_get_active_seeders): mock_get_active_seeders.return_value = [self.SampleSeed1] before = list(ResourceURI.objects.all().values_list('id')) @@ -239,6 +239,6 @@ def test_get_active_seeders(self): def test_get_configured_seeders(self): seeders = seed.get_configured_seeders() expected = [ - 'discovery.visitors.npm.NpmSeed', + 'minecode.visitors.npm.NpmSeed', ] assert sorted(expected) == sorted(seeders) diff --git a/discovery/tests/test_sourceforge.py b/minecode/tests/test_sourceforge.py similarity index 96% rename from discovery/tests/test_sourceforge.py rename to minecode/tests/test_sourceforge.py index ad78be41..9cd29bc1 100644 --- a/discovery/tests/test_sourceforge.py +++ b/minecode/tests/test_sourceforge.py @@ -12,11 +12,11 @@ from mock import patch -from discovery.utils_test import mocked_requests_get -from discovery.utils_test import JsonBasedTesting +from minecode.utils_test import mocked_requests_get +from minecode.utils_test import JsonBasedTesting -from discovery import mappers -from discovery.visitors import sourceforge +from minecode import mappers +from minecode.visitors import sourceforge class SourceforgeVisitorsTest(JsonBasedTesting): diff --git a/discovery/tests/test_utils.py b/minecode/tests/test_utils.py similarity index 93% rename from discovery/tests/test_utils.py rename to minecode/tests/test_utils.py index e08826a8..66e0dde9 100644 --- a/discovery/tests/test_utils.py +++ b/minecode/tests/test_utils.py @@ -14,9 +14,9 @@ from packagedcode import models as scan_models -from discovery.utils_test import JsonBasedTesting -from discovery.utils import is_int -from discovery.utils import stringify_null_purl_fields +from minecode.utils_test import JsonBasedTesting +from minecode.utils import is_int +from minecode.utils import stringify_null_purl_fields class UtilsTest(JsonBasedTesting, DjangoTestCase): diff --git a/discovery/tests/test_version.py b/minecode/tests/test_version.py similarity index 99% rename from discovery/tests/test_version.py rename to minecode/tests/test_version.py index c4005083..3e66fef6 100644 --- a/discovery/tests/test_version.py +++ b/minecode/tests/test_version.py @@ -13,7 +13,7 @@ import unittest from unittest.case import expectedFailure -from discovery.version import version_hint +from minecode.version import version_hint class VersionHintTestCase(unittest.TestCase): diff --git a/discovery/tests/testfiles/command/bar b/minecode/tests/testfiles/command/bar similarity index 100% rename from discovery/tests/testfiles/command/bar rename to minecode/tests/testfiles/command/bar diff --git a/discovery/tests/testfiles/command/foo/.gitignore b/minecode/tests/testfiles/command/foo/.gitignore similarity index 100% rename from discovery/tests/testfiles/command/foo/.gitignore rename to minecode/tests/testfiles/command/foo/.gitignore diff --git a/discovery/tests/testfiles/debian/copyright/basic_copyright b/minecode/tests/testfiles/debian/copyright/basic_copyright similarity index 100% rename from discovery/tests/testfiles/debian/copyright/basic_copyright rename to minecode/tests/testfiles/debian/copyright/basic_copyright diff --git a/discovery/tests/testfiles/debian/copyright/copyright_abiword_common_copyright-abiword_common_copyright.copyright b/minecode/tests/testfiles/debian/copyright/copyright_abiword_common_copyright-abiword_common_copyright.copyright similarity index 100% rename from discovery/tests/testfiles/debian/copyright/copyright_abiword_common_copyright-abiword_common_copyright.copyright rename to minecode/tests/testfiles/debian/copyright/copyright_abiword_common_copyright-abiword_common_copyright.copyright diff --git a/discovery/tests/testfiles/debian/copyright/invalid_copyright b/minecode/tests/testfiles/debian/copyright/invalid_copyright similarity index 100% rename from discovery/tests/testfiles/debian/copyright/invalid_copyright rename to minecode/tests/testfiles/debian/copyright/invalid_copyright diff --git a/discovery/tests/testfiles/debian/debian_lslrs_expected b/minecode/tests/testfiles/debian/debian_lslrs_expected similarity index 100% rename from discovery/tests/testfiles/debian/debian_lslrs_expected rename to minecode/tests/testfiles/debian/debian_lslrs_expected diff --git a/discovery/tests/testfiles/debian/debian_lslrs_on_ubuntu_expected b/minecode/tests/testfiles/debian/debian_lslrs_on_ubuntu_expected similarity index 100% rename from discovery/tests/testfiles/debian/debian_lslrs_on_ubuntu_expected rename to minecode/tests/testfiles/debian/debian_lslrs_on_ubuntu_expected diff --git a/discovery/tests/testfiles/debian/debian_sourceindex_expected b/minecode/tests/testfiles/debian/debian_sourceindex_expected similarity index 100% rename from discovery/tests/testfiles/debian/debian_sourceindex_expected rename to minecode/tests/testfiles/debian/debian_sourceindex_expected diff --git a/discovery/tests/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc b/minecode/tests/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc similarity index 100% rename from discovery/tests/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc rename to minecode/tests/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc diff --git a/discovery/tests/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc-expected b/minecode/tests/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc-expected similarity index 100% rename from discovery/tests/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc-expected rename to minecode/tests/testfiles/debian/debutils/3dldf_2.0.3+dfsg-2.dsc-expected diff --git a/discovery/tests/testfiles/debian/debutils/control_basic b/minecode/tests/testfiles/debian/debutils/control_basic similarity index 100% rename from discovery/tests/testfiles/debian/debutils/control_basic rename to minecode/tests/testfiles/debian/debutils/control_basic diff --git a/discovery/tests/testfiles/debian/debutils/control_invalid b/minecode/tests/testfiles/debian/debutils/control_invalid similarity index 100% rename from discovery/tests/testfiles/debian/debutils/control_invalid rename to minecode/tests/testfiles/debian/debutils/control_invalid diff --git a/discovery/tests/testfiles/debian/dsc/7kaa_2.14.3-1.dsc b/minecode/tests/testfiles/debian/dsc/7kaa_2.14.3-1.dsc similarity index 100% rename from discovery/tests/testfiles/debian/dsc/7kaa_2.14.3-1.dsc rename to minecode/tests/testfiles/debian/dsc/7kaa_2.14.3-1.dsc diff --git a/discovery/tests/testfiles/debian/dsc/description-expected.json b/minecode/tests/testfiles/debian/dsc/description-expected.json similarity index 100% rename from discovery/tests/testfiles/debian/dsc/description-expected.json rename to minecode/tests/testfiles/debian/dsc/description-expected.json diff --git a/discovery/tests/testfiles/debian/dsc/description.json b/minecode/tests/testfiles/debian/dsc/description.json similarity index 100% rename from discovery/tests/testfiles/debian/dsc/description.json rename to minecode/tests/testfiles/debian/dsc/description.json diff --git a/discovery/tests/testfiles/debian/dsc/description_expected.json b/minecode/tests/testfiles/debian/dsc/description_expected.json similarity index 100% rename from discovery/tests/testfiles/debian/dsc/description_expected.json rename to minecode/tests/testfiles/debian/dsc/description_expected.json diff --git a/discovery/tests/testfiles/debian/dsc/invalid.dsc b/minecode/tests/testfiles/debian/dsc/invalid.dsc similarity index 100% rename from discovery/tests/testfiles/debian/dsc/invalid.dsc rename to minecode/tests/testfiles/debian/dsc/invalid.dsc diff --git a/discovery/tests/testfiles/debian/invalid_files/ls-lR.gz b/minecode/tests/testfiles/debian/invalid_files/ls-lR.gz similarity index 100% rename from discovery/tests/testfiles/debian/invalid_files/ls-lR.gz rename to minecode/tests/testfiles/debian/invalid_files/ls-lR.gz diff --git a/discovery/tests/testfiles/debian/lslr/ls-lR_debian.gz b/minecode/tests/testfiles/debian/lslr/ls-lR_debian.gz similarity index 100% rename from discovery/tests/testfiles/debian/lslr/ls-lR_debian.gz rename to minecode/tests/testfiles/debian/lslr/ls-lR_debian.gz diff --git a/discovery/tests/testfiles/debian/lslr/ls-lR_debian.gz-expected.json b/minecode/tests/testfiles/debian/lslr/ls-lR_debian.gz-expected.json similarity index 100% rename from discovery/tests/testfiles/debian/lslr/ls-lR_debian.gz-expected.json rename to minecode/tests/testfiles/debian/lslr/ls-lR_debian.gz-expected.json diff --git a/discovery/tests/testfiles/debian/lslr/ls-lR_ubuntu.gz b/minecode/tests/testfiles/debian/lslr/ls-lR_ubuntu.gz similarity index 100% rename from discovery/tests/testfiles/debian/lslr/ls-lR_ubuntu.gz rename to minecode/tests/testfiles/debian/lslr/ls-lR_ubuntu.gz diff --git a/discovery/tests/testfiles/debian/lslr/ls-lR_ubuntu.gz-expected.json b/minecode/tests/testfiles/debian/lslr/ls-lR_ubuntu.gz-expected.json similarity index 100% rename from discovery/tests/testfiles/debian/lslr/ls-lR_ubuntu.gz-expected.json rename to minecode/tests/testfiles/debian/lslr/ls-lR_ubuntu.gz-expected.json diff --git a/discovery/tests/testfiles/debian/packages/debian_Packages b/minecode/tests/testfiles/debian/packages/debian_Packages similarity index 100% rename from discovery/tests/testfiles/debian/packages/debian_Packages rename to minecode/tests/testfiles/debian/packages/debian_Packages diff --git a/discovery/tests/testfiles/debian/packages/debian_Packages-expected.json b/minecode/tests/testfiles/debian/packages/debian_Packages-expected.json similarity index 100% rename from discovery/tests/testfiles/debian/packages/debian_Packages-expected.json rename to minecode/tests/testfiles/debian/packages/debian_Packages-expected.json diff --git a/discovery/tests/testfiles/debian/packages/debian_Packages-visit-expected.json b/minecode/tests/testfiles/debian/packages/debian_Packages-visit-expected.json similarity index 100% rename from discovery/tests/testfiles/debian/packages/debian_Packages-visit-expected.json rename to minecode/tests/testfiles/debian/packages/debian_Packages-visit-expected.json diff --git a/discovery/tests/testfiles/debian/packages/ubuntu_Packages b/minecode/tests/testfiles/debian/packages/ubuntu_Packages similarity index 100% rename from discovery/tests/testfiles/debian/packages/ubuntu_Packages rename to minecode/tests/testfiles/debian/packages/ubuntu_Packages diff --git a/discovery/tests/testfiles/debian/packages/ubuntu_Packages-expected.json b/minecode/tests/testfiles/debian/packages/ubuntu_Packages-expected.json similarity index 100% rename from discovery/tests/testfiles/debian/packages/ubuntu_Packages-expected.json rename to minecode/tests/testfiles/debian/packages/ubuntu_Packages-expected.json diff --git a/discovery/tests/testfiles/debian/release/Release b/minecode/tests/testfiles/debian/release/Release similarity index 100% rename from discovery/tests/testfiles/debian/release/Release rename to minecode/tests/testfiles/debian/release/Release diff --git a/discovery/tests/testfiles/debian/release/Release_expected b/minecode/tests/testfiles/debian/release/Release_expected similarity index 100% rename from discovery/tests/testfiles/debian/release/Release_expected rename to minecode/tests/testfiles/debian/release/Release_expected diff --git a/discovery/tests/testfiles/debian/release/Release_with_md5 b/minecode/tests/testfiles/debian/release/Release_with_md5 similarity index 100% rename from discovery/tests/testfiles/debian/release/Release_with_md5 rename to minecode/tests/testfiles/debian/release/Release_with_md5 diff --git a/discovery/tests/testfiles/debian/release/Release_with_md5_expected b/minecode/tests/testfiles/debian/release/Release_with_md5_expected similarity index 100% rename from discovery/tests/testfiles/debian/release/Release_with_md5_expected rename to minecode/tests/testfiles/debian/release/Release_with_md5_expected diff --git a/discovery/tests/testfiles/debian/release/visited_Release b/minecode/tests/testfiles/debian/release/visited_Release similarity index 100% rename from discovery/tests/testfiles/debian/release/visited_Release rename to minecode/tests/testfiles/debian/release/visited_Release diff --git a/discovery/tests/testfiles/debian/release/visited_Release-expected.json b/minecode/tests/testfiles/debian/release/visited_Release-expected.json similarity index 100% rename from discovery/tests/testfiles/debian/release/visited_Release-expected.json rename to minecode/tests/testfiles/debian/release/visited_Release-expected.json diff --git a/discovery/tests/testfiles/debian/sources/Sources.gz b/minecode/tests/testfiles/debian/sources/Sources.gz similarity index 100% rename from discovery/tests/testfiles/debian/sources/Sources.gz rename to minecode/tests/testfiles/debian/sources/Sources.gz diff --git a/discovery/tests/testfiles/debian/sources/Sources.gz-expected.json b/minecode/tests/testfiles/debian/sources/Sources.gz-expected.json similarity index 100% rename from discovery/tests/testfiles/debian/sources/Sources.gz-expected.json rename to minecode/tests/testfiles/debian/sources/Sources.gz-expected.json diff --git a/discovery/tests/testfiles/debian/sources/debian_Sources b/minecode/tests/testfiles/debian/sources/debian_Sources similarity index 100% rename from discovery/tests/testfiles/debian/sources/debian_Sources rename to minecode/tests/testfiles/debian/sources/debian_Sources diff --git a/discovery/tests/testfiles/debian/sources/debian_Sources_mapped-expected-packages.json b/minecode/tests/testfiles/debian/sources/debian_Sources_mapped-expected-packages.json similarity index 100% rename from discovery/tests/testfiles/debian/sources/debian_Sources_mapped-expected-packages.json rename to minecode/tests/testfiles/debian/sources/debian_Sources_mapped-expected-packages.json diff --git a/discovery/tests/testfiles/debian/sources/debian_Sources_visit_expected b/minecode/tests/testfiles/debian/sources/debian_Sources_visit_expected similarity index 100% rename from discovery/tests/testfiles/debian/sources/debian_Sources_visit_expected rename to minecode/tests/testfiles/debian/sources/debian_Sources_visit_expected diff --git a/discovery/tests/testfiles/debian/sources/ubuntu_Sources b/minecode/tests/testfiles/debian/sources/ubuntu_Sources similarity index 100% rename from discovery/tests/testfiles/debian/sources/ubuntu_Sources rename to minecode/tests/testfiles/debian/sources/ubuntu_Sources diff --git a/discovery/tests/testfiles/debian/sources/ubuntu_Sources_visit_expected b/minecode/tests/testfiles/debian/sources/ubuntu_Sources_visit_expected similarity index 100% rename from discovery/tests/testfiles/debian/sources/ubuntu_Sources_visit_expected rename to minecode/tests/testfiles/debian/sources/ubuntu_Sources_visit_expected diff --git a/discovery/tests/testfiles/debian/status/simple_status b/minecode/tests/testfiles/debian/status/simple_status similarity index 100% rename from discovery/tests/testfiles/debian/status/simple_status rename to minecode/tests/testfiles/debian/status/simple_status diff --git a/discovery/tests/testfiles/directories/find-ls b/minecode/tests/testfiles/directories/find-ls similarity index 100% rename from discovery/tests/testfiles/directories/find-ls rename to minecode/tests/testfiles/directories/find-ls diff --git a/discovery/tests/testfiles/directories/find-ls-apache-start b/minecode/tests/testfiles/directories/find-ls-apache-start similarity index 100% rename from discovery/tests/testfiles/directories/find-ls-apache-start rename to minecode/tests/testfiles/directories/find-ls-apache-start diff --git a/discovery/tests/testfiles/directories/find-ls-apache-start-expected.json b/minecode/tests/testfiles/directories/find-ls-apache-start-expected.json similarity index 100% rename from discovery/tests/testfiles/directories/find-ls-apache-start-expected.json rename to minecode/tests/testfiles/directories/find-ls-apache-start-expected.json diff --git a/discovery/tests/testfiles/directories/find-ls-expected.json b/minecode/tests/testfiles/directories/find-ls-expected.json similarity index 100% rename from discovery/tests/testfiles/directories/find-ls-expected.json rename to minecode/tests/testfiles/directories/find-ls-expected.json diff --git a/discovery/tests/testfiles/directories/ls-lr b/minecode/tests/testfiles/directories/ls-lr similarity index 100% rename from discovery/tests/testfiles/directories/ls-lr rename to minecode/tests/testfiles/directories/ls-lr diff --git a/discovery/tests/testfiles/directories/ls-lr-expected.json b/minecode/tests/testfiles/directories/ls-lr-expected.json similarity index 100% rename from discovery/tests/testfiles/directories/ls-lr-expected.json rename to minecode/tests/testfiles/directories/ls-lr-expected.json diff --git a/discovery/tests/testfiles/directories/ls-lr-ubuntu b/minecode/tests/testfiles/directories/ls-lr-ubuntu similarity index 100% rename from discovery/tests/testfiles/directories/ls-lr-ubuntu rename to minecode/tests/testfiles/directories/ls-lr-ubuntu diff --git a/discovery/tests/testfiles/directories/ls-lr-ubuntu-expected.json b/minecode/tests/testfiles/directories/ls-lr-ubuntu-expected.json similarity index 100% rename from discovery/tests/testfiles/directories/ls-lr-ubuntu-expected.json rename to minecode/tests/testfiles/directories/ls-lr-ubuntu-expected.json diff --git a/discovery/tests/testfiles/fdroid/index-v2-expected-visit.json b/minecode/tests/testfiles/fdroid/index-v2-expected-visit.json similarity index 100% rename from discovery/tests/testfiles/fdroid/index-v2-expected-visit.json rename to minecode/tests/testfiles/fdroid/index-v2-expected-visit.json diff --git a/discovery/tests/testfiles/fdroid/index-v2-visited-expected-mapped.json b/minecode/tests/testfiles/fdroid/index-v2-visited-expected-mapped.json similarity index 100% rename from discovery/tests/testfiles/fdroid/index-v2-visited-expected-mapped.json rename to minecode/tests/testfiles/fdroid/index-v2-visited-expected-mapped.json diff --git a/discovery/tests/testfiles/fdroid/index-v2-visited.json b/minecode/tests/testfiles/fdroid/index-v2-visited.json similarity index 100% rename from discovery/tests/testfiles/fdroid/index-v2-visited.json rename to minecode/tests/testfiles/fdroid/index-v2-visited.json diff --git a/discovery/tests/testfiles/fdroid/index-v2.json b/minecode/tests/testfiles/fdroid/index-v2.json similarity index 100% rename from discovery/tests/testfiles/fdroid/index-v2.json rename to minecode/tests/testfiles/fdroid/index-v2.json diff --git a/discovery/tests/testfiles/freebsd/FreeBSD-10-i386_release_0_.html b/minecode/tests/testfiles/freebsd/FreeBSD-10-i386_release_0_.html similarity index 100% rename from discovery/tests/testfiles/freebsd/FreeBSD-10-i386_release_0_.html rename to minecode/tests/testfiles/freebsd/FreeBSD-10-i386_release_0_.html diff --git a/discovery/tests/testfiles/freebsd/FreeBSD-10-i386_release_0_.html_expected b/minecode/tests/testfiles/freebsd/FreeBSD-10-i386_release_0_.html_expected similarity index 100% rename from discovery/tests/testfiles/freebsd/FreeBSD-10-i386_release_0_.html_expected rename to minecode/tests/testfiles/freebsd/FreeBSD-10-i386_release_0_.html_expected diff --git a/discovery/tests/testfiles/freebsd/FreeBSD.org.html b/minecode/tests/testfiles/freebsd/FreeBSD.org.html similarity index 100% rename from discovery/tests/testfiles/freebsd/FreeBSD.org.html rename to minecode/tests/testfiles/freebsd/FreeBSD.org.html diff --git a/discovery/tests/testfiles/freebsd/FreeBSD.org.html_expected b/minecode/tests/testfiles/freebsd/FreeBSD.org.html_expected similarity index 100% rename from discovery/tests/testfiles/freebsd/FreeBSD.org.html_expected rename to minecode/tests/testfiles/freebsd/FreeBSD.org.html_expected diff --git a/discovery/tests/testfiles/freebsd/indexfile_expected b/minecode/tests/testfiles/freebsd/indexfile_expected similarity index 100% rename from discovery/tests/testfiles/freebsd/indexfile_expected rename to minecode/tests/testfiles/freebsd/indexfile_expected diff --git a/discovery/tests/testfiles/freebsd/indexfile_expected_mapper.json b/minecode/tests/testfiles/freebsd/indexfile_expected_mapper.json similarity index 100% rename from discovery/tests/testfiles/freebsd/indexfile_expected_mapper.json rename to minecode/tests/testfiles/freebsd/indexfile_expected_mapper.json diff --git a/discovery/tests/testfiles/freebsd/mapper_input1 b/minecode/tests/testfiles/freebsd/mapper_input1 similarity index 100% rename from discovery/tests/testfiles/freebsd/mapper_input1 rename to minecode/tests/testfiles/freebsd/mapper_input1 diff --git a/discovery/tests/testfiles/freebsd/packagesite.txz b/minecode/tests/testfiles/freebsd/packagesite.txz similarity index 100% rename from discovery/tests/testfiles/freebsd/packagesite.txz rename to minecode/tests/testfiles/freebsd/packagesite.txz diff --git a/discovery/tests/testfiles/freebsd/pkg-devel_index b/minecode/tests/testfiles/freebsd/pkg-devel_index similarity index 100% rename from discovery/tests/testfiles/freebsd/pkg-devel_index rename to minecode/tests/testfiles/freebsd/pkg-devel_index diff --git a/discovery/tests/testfiles/freebsd/pkg-devel_index_mapper.json b/minecode/tests/testfiles/freebsd/pkg-devel_index_mapper.json similarity index 100% rename from discovery/tests/testfiles/freebsd/pkg-devel_index_mapper.json rename to minecode/tests/testfiles/freebsd/pkg-devel_index_mapper.json diff --git a/discovery/tests/testfiles/freebsd/squirrelmail-plugins-1.0_2.txz b/minecode/tests/testfiles/freebsd/squirrelmail-plugins-1.0_2.txz similarity index 100% rename from discovery/tests/testfiles/freebsd/squirrelmail-plugins-1.0_2.txz rename to minecode/tests/testfiles/freebsd/squirrelmail-plugins-1.0_2.txz diff --git a/discovery/tests/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom b/minecode/tests/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom similarity index 100% rename from discovery/tests/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom rename to minecode/tests/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom diff --git a/discovery/tests/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom_search_expected.json b/minecode/tests/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom_search_expected.json similarity index 100% rename from discovery/tests/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom_search_expected.json rename to minecode/tests/testfiles/housekeeping/bytejta-supports-0.5.0-ALPHA4.pom_search_expected.json diff --git a/discovery/tests/testfiles/housekeeping/declared_license_search_expected.json b/minecode/tests/testfiles/housekeeping/declared_license_search_expected.json similarity index 100% rename from discovery/tests/testfiles/housekeeping/declared_license_search_expected.json rename to minecode/tests/testfiles/housekeeping/declared_license_search_expected.json diff --git a/discovery/tests/testfiles/housekeeping/example_expected.json b/minecode/tests/testfiles/housekeeping/example_expected.json similarity index 100% rename from discovery/tests/testfiles/housekeeping/example_expected.json rename to minecode/tests/testfiles/housekeeping/example_expected.json diff --git a/discovery/tests/testfiles/housekeeping/ignore_upper_case_search_expected.json b/minecode/tests/testfiles/housekeeping/ignore_upper_case_search_expected.json similarity index 100% rename from discovery/tests/testfiles/housekeeping/ignore_upper_case_search_expected.json rename to minecode/tests/testfiles/housekeeping/ignore_upper_case_search_expected.json diff --git a/discovery/tests/testfiles/housekeeping/license_expression_search_expected.json b/minecode/tests/testfiles/housekeeping/license_expression_search_expected.json similarity index 100% rename from discovery/tests/testfiles/housekeeping/license_expression_search_expected.json rename to minecode/tests/testfiles/housekeeping/license_expression_search_expected.json diff --git a/discovery/tests/testfiles/maven/end2end/bytejta-supports-0.5.0-ALPHA4.pom b/minecode/tests/testfiles/maven/end2end/bytejta-supports-0.5.0-ALPHA4.pom similarity index 100% rename from discovery/tests/testfiles/maven/end2end/bytejta-supports-0.5.0-ALPHA4.pom rename to minecode/tests/testfiles/maven/end2end/bytejta-supports-0.5.0-ALPHA4.pom diff --git a/discovery/tests/testfiles/maven/end2end/expected_mapped_packages.json b/minecode/tests/testfiles/maven/end2end/expected_mapped_packages.json similarity index 100% rename from discovery/tests/testfiles/maven/end2end/expected_mapped_packages.json rename to minecode/tests/testfiles/maven/end2end/expected_mapped_packages.json diff --git a/discovery/tests/testfiles/maven/end2end/expected_visited_uris.json b/minecode/tests/testfiles/maven/end2end/expected_visited_uris.json similarity index 100% rename from discovery/tests/testfiles/maven/end2end/expected_visited_uris.json rename to minecode/tests/testfiles/maven/end2end/expected_visited_uris.json diff --git a/discovery/tests/testfiles/maven/end2end/test_uris.json b/minecode/tests/testfiles/maven/end2end/test_uris.json similarity index 100% rename from discovery/tests/testfiles/maven/end2end/test_uris.json rename to minecode/tests/testfiles/maven/end2end/test_uris.json diff --git a/discovery/tests/testfiles/maven/end2end_index/expected_visited_increment_index.json b/minecode/tests/testfiles/maven/end2end_index/expected_visited_increment_index.json similarity index 100% rename from discovery/tests/testfiles/maven/end2end_index/expected_visited_increment_index.json rename to minecode/tests/testfiles/maven/end2end_index/expected_visited_increment_index.json diff --git a/discovery/tests/testfiles/maven/end2end_index/expected_visited_index.json b/minecode/tests/testfiles/maven/end2end_index/expected_visited_index.json similarity index 100% rename from discovery/tests/testfiles/maven/end2end_index/expected_visited_index.json rename to minecode/tests/testfiles/maven/end2end_index/expected_visited_index.json diff --git a/discovery/tests/testfiles/maven/end2end_index/nexus-maven-repository-index.163.gz b/minecode/tests/testfiles/maven/end2end_index/nexus-maven-repository-index.163.gz similarity index 100% rename from discovery/tests/testfiles/maven/end2end_index/nexus-maven-repository-index.163.gz rename to minecode/tests/testfiles/maven/end2end_index/nexus-maven-repository-index.163.gz diff --git a/discovery/tests/testfiles/maven/end2end_index/nexus-maven-repository-index.properties b/minecode/tests/testfiles/maven/end2end_index/nexus-maven-repository-index.properties similarity index 100% rename from discovery/tests/testfiles/maven/end2end_index/nexus-maven-repository-index.properties rename to minecode/tests/testfiles/maven/end2end_index/nexus-maven-repository-index.properties diff --git a/discovery/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-index-data.json b/minecode/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-index-data.json similarity index 100% rename from discovery/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-index-data.json rename to minecode/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-index-data.json diff --git a/discovery/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-pom-data.json b/minecode/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-pom-data.json similarity index 100% rename from discovery/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-pom-data.json rename to minecode/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21-pom-data.json diff --git a/discovery/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21.pom b/minecode/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21.pom similarity index 100% rename from discovery/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21.pom rename to minecode/tests/testfiles/maven/end2end_multisteps/commons-jaxrs-1.21.pom diff --git a/discovery/tests/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-index.json b/minecode/tests/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-index.json similarity index 100% rename from discovery/tests/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-index.json rename to minecode/tests/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-index.json diff --git a/discovery/tests/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-pom.json b/minecode/tests/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-pom.json similarity index 100% rename from discovery/tests/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-pom.json rename to minecode/tests/testfiles/maven/end2end_multisteps/expected_mapped_commons-jaxrs-1.21-from-pom.json diff --git a/discovery/tests/testfiles/maven/end2end_unicode/commons-jaxrs-1.22.pom b/minecode/tests/testfiles/maven/end2end_unicode/commons-jaxrs-1.22.pom similarity index 100% rename from discovery/tests/testfiles/maven/end2end_unicode/commons-jaxrs-1.22.pom rename to minecode/tests/testfiles/maven/end2end_unicode/commons-jaxrs-1.22.pom diff --git a/discovery/tests/testfiles/maven/end2end_unicode/expected_mapped_commons-jaxrs-1.22.json b/minecode/tests/testfiles/maven/end2end_unicode/expected_mapped_commons-jaxrs-1.22.json similarity index 100% rename from discovery/tests/testfiles/maven/end2end_unicode/expected_mapped_commons-jaxrs-1.22.json rename to minecode/tests/testfiles/maven/end2end_unicode/expected_mapped_commons-jaxrs-1.22.json diff --git a/discovery/tests/testfiles/maven/end2end_unicode/expected_visited_commons-jaxrs-1.22.json b/minecode/tests/testfiles/maven/end2end_unicode/expected_visited_commons-jaxrs-1.22.json similarity index 100% rename from discovery/tests/testfiles/maven/end2end_unicode/expected_visited_commons-jaxrs-1.22.json rename to minecode/tests/testfiles/maven/end2end_unicode/expected_visited_commons-jaxrs-1.22.json diff --git a/discovery/tests/testfiles/maven/html/app.html b/minecode/tests/testfiles/maven/html/app.html similarity index 100% rename from discovery/tests/testfiles/maven/html/app.html rename to minecode/tests/testfiles/maven/html/app.html diff --git a/discovery/tests/testfiles/maven/html/jcenter.bintray.com.html b/minecode/tests/testfiles/maven/html/jcenter.bintray.com.html similarity index 100% rename from discovery/tests/testfiles/maven/html/jcenter.bintray.com.html rename to minecode/tests/testfiles/maven/html/jcenter.bintray.com.html diff --git a/discovery/tests/testfiles/maven/html/stateframework-compiler.html b/minecode/tests/testfiles/maven/html/stateframework-compiler.html similarity index 100% rename from discovery/tests/testfiles/maven/html/stateframework-compiler.html rename to minecode/tests/testfiles/maven/html/stateframework-compiler.html diff --git a/discovery/tests/testfiles/maven/html/visitor_expected_app.html.json b/minecode/tests/testfiles/maven/html/visitor_expected_app.html.json similarity index 100% rename from discovery/tests/testfiles/maven/html/visitor_expected_app.html.json rename to minecode/tests/testfiles/maven/html/visitor_expected_app.html.json diff --git a/discovery/tests/testfiles/maven/html/visitor_expected_jcenter.bintray.com2.html.json b/minecode/tests/testfiles/maven/html/visitor_expected_jcenter.bintray.com2.html.json similarity index 100% rename from discovery/tests/testfiles/maven/html/visitor_expected_jcenter.bintray.com2.html.json rename to minecode/tests/testfiles/maven/html/visitor_expected_jcenter.bintray.com2.html.json diff --git a/discovery/tests/testfiles/maven/html/visitor_expected_stateframework-compiler.html.json b/minecode/tests/testfiles/maven/html/visitor_expected_stateframework-compiler.html.json similarity index 100% rename from discovery/tests/testfiles/maven/html/visitor_expected_stateframework-compiler.html.json rename to minecode/tests/testfiles/maven/html/visitor_expected_stateframework-compiler.html.json diff --git a/discovery/tests/testfiles/maven/index/buggy/expected_artifacts-defaults.json b/minecode/tests/testfiles/maven/index/buggy/expected_artifacts-defaults.json similarity index 100% rename from discovery/tests/testfiles/maven/index/buggy/expected_artifacts-defaults.json rename to minecode/tests/testfiles/maven/index/buggy/expected_artifacts-defaults.json diff --git a/discovery/tests/testfiles/maven/index/buggy/expected_artifacts.json b/minecode/tests/testfiles/maven/index/buggy/expected_artifacts.json similarity index 100% rename from discovery/tests/testfiles/maven/index/buggy/expected_artifacts.json rename to minecode/tests/testfiles/maven/index/buggy/expected_artifacts.json diff --git a/discovery/tests/testfiles/maven/index/buggy/expected_entries.json b/minecode/tests/testfiles/maven/index/buggy/expected_entries.json similarity index 100% rename from discovery/tests/testfiles/maven/index/buggy/expected_entries.json rename to minecode/tests/testfiles/maven/index/buggy/expected_entries.json diff --git a/discovery/tests/testfiles/maven/index/buggy/expected_uris.json b/minecode/tests/testfiles/maven/index/buggy/expected_uris.json similarity index 100% rename from discovery/tests/testfiles/maven/index/buggy/expected_uris.json rename to minecode/tests/testfiles/maven/index/buggy/expected_uris.json diff --git a/discovery/tests/testfiles/maven/index/buggy/expected_visited_uris.json b/minecode/tests/testfiles/maven/index/buggy/expected_visited_uris.json similarity index 100% rename from discovery/tests/testfiles/maven/index/buggy/expected_visited_uris.json rename to minecode/tests/testfiles/maven/index/buggy/expected_visited_uris.json diff --git a/discovery/tests/testfiles/maven/index/buggy/nexus-maven-repository-index.gz b/minecode/tests/testfiles/maven/index/buggy/nexus-maven-repository-index.gz similarity index 100% rename from discovery/tests/testfiles/maven/index/buggy/nexus-maven-repository-index.gz rename to minecode/tests/testfiles/maven/index/buggy/nexus-maven-repository-index.gz diff --git a/discovery/tests/testfiles/maven/index/expected_artifacts-all-worthy.json b/minecode/tests/testfiles/maven/index/expected_artifacts-all-worthy.json similarity index 100% rename from discovery/tests/testfiles/maven/index/expected_artifacts-all-worthy.json rename to minecode/tests/testfiles/maven/index/expected_artifacts-all-worthy.json diff --git a/discovery/tests/testfiles/maven/index/expected_artifacts-defaults.json b/minecode/tests/testfiles/maven/index/expected_artifacts-defaults.json similarity index 100% rename from discovery/tests/testfiles/maven/index/expected_artifacts-defaults.json rename to minecode/tests/testfiles/maven/index/expected_artifacts-defaults.json diff --git a/discovery/tests/testfiles/maven/index/expected_artifacts.json b/minecode/tests/testfiles/maven/index/expected_artifacts.json similarity index 100% rename from discovery/tests/testfiles/maven/index/expected_artifacts.json rename to minecode/tests/testfiles/maven/index/expected_artifacts.json diff --git a/discovery/tests/testfiles/maven/index/expected_entries.json b/minecode/tests/testfiles/maven/index/expected_entries.json similarity index 100% rename from discovery/tests/testfiles/maven/index/expected_entries.json rename to minecode/tests/testfiles/maven/index/expected_entries.json diff --git a/discovery/tests/testfiles/maven/index/expected_uris.json b/minecode/tests/testfiles/maven/index/expected_uris.json similarity index 100% rename from discovery/tests/testfiles/maven/index/expected_uris.json rename to minecode/tests/testfiles/maven/index/expected_uris.json diff --git a/discovery/tests/testfiles/maven/index/increment/expected_artifacts-defaults.json b/minecode/tests/testfiles/maven/index/increment/expected_artifacts-defaults.json similarity index 100% rename from discovery/tests/testfiles/maven/index/increment/expected_artifacts-defaults.json rename to minecode/tests/testfiles/maven/index/increment/expected_artifacts-defaults.json diff --git a/discovery/tests/testfiles/maven/index/increment/expected_artifacts.json b/minecode/tests/testfiles/maven/index/increment/expected_artifacts.json similarity index 100% rename from discovery/tests/testfiles/maven/index/increment/expected_artifacts.json rename to minecode/tests/testfiles/maven/index/increment/expected_artifacts.json diff --git a/discovery/tests/testfiles/maven/index/increment/expected_entries.json b/minecode/tests/testfiles/maven/index/increment/expected_entries.json similarity index 100% rename from discovery/tests/testfiles/maven/index/increment/expected_entries.json rename to minecode/tests/testfiles/maven/index/increment/expected_entries.json diff --git a/discovery/tests/testfiles/maven/index/increment/expected_properties_uris.json b/minecode/tests/testfiles/maven/index/increment/expected_properties_uris.json similarity index 100% rename from discovery/tests/testfiles/maven/index/increment/expected_properties_uris.json rename to minecode/tests/testfiles/maven/index/increment/expected_properties_uris.json diff --git a/discovery/tests/testfiles/maven/index/increment/expected_uris.json b/minecode/tests/testfiles/maven/index/increment/expected_uris.json similarity index 100% rename from discovery/tests/testfiles/maven/index/increment/expected_uris.json rename to minecode/tests/testfiles/maven/index/increment/expected_uris.json diff --git a/discovery/tests/testfiles/maven/index/increment/nexus-maven-repository-index.445.gz b/minecode/tests/testfiles/maven/index/increment/nexus-maven-repository-index.445.gz similarity index 100% rename from discovery/tests/testfiles/maven/index/increment/nexus-maven-repository-index.445.gz rename to minecode/tests/testfiles/maven/index/increment/nexus-maven-repository-index.445.gz diff --git a/discovery/tests/testfiles/maven/index/increment/nexus-maven-repository-index.properties b/minecode/tests/testfiles/maven/index/increment/nexus-maven-repository-index.properties similarity index 100% rename from discovery/tests/testfiles/maven/index/increment/nexus-maven-repository-index.properties rename to minecode/tests/testfiles/maven/index/increment/nexus-maven-repository-index.properties diff --git a/discovery/tests/testfiles/maven/index/increment2/expected_mini_package.json b/minecode/tests/testfiles/maven/index/increment2/expected_mini_package.json similarity index 100% rename from discovery/tests/testfiles/maven/index/increment2/expected_mini_package.json rename to minecode/tests/testfiles/maven/index/increment2/expected_mini_package.json diff --git a/discovery/tests/testfiles/maven/index/increment2/expected_uris.json b/minecode/tests/testfiles/maven/index/increment2/expected_uris.json similarity index 100% rename from discovery/tests/testfiles/maven/index/increment2/expected_uris.json rename to minecode/tests/testfiles/maven/index/increment2/expected_uris.json diff --git a/discovery/tests/testfiles/maven/index/increment2/nexus-maven-repository-index.457.gz b/minecode/tests/testfiles/maven/index/increment2/nexus-maven-repository-index.457.gz similarity index 100% rename from discovery/tests/testfiles/maven/index/increment2/nexus-maven-repository-index.457.gz rename to minecode/tests/testfiles/maven/index/increment2/nexus-maven-repository-index.457.gz diff --git a/discovery/tests/testfiles/maven/index/nexus-maven-repository-index.gz b/minecode/tests/testfiles/maven/index/nexus-maven-repository-index.gz similarity index 100% rename from discovery/tests/testfiles/maven/index/nexus-maven-repository-index.gz rename to minecode/tests/testfiles/maven/index/nexus-maven-repository-index.gz diff --git a/discovery/tests/testfiles/maven/mapper/ant-1.6.5.pom b/minecode/tests/testfiles/maven/mapper/ant-1.6.5.pom similarity index 100% rename from discovery/tests/testfiles/maven/mapper/ant-1.6.5.pom rename to minecode/tests/testfiles/maven/mapper/ant-1.6.5.pom diff --git a/discovery/tests/testfiles/maven/mapper/ant-1.6.5.pom.json b/minecode/tests/testfiles/maven/mapper/ant-1.6.5.pom.json similarity index 100% rename from discovery/tests/testfiles/maven/mapper/ant-1.6.5.pom.json rename to minecode/tests/testfiles/maven/mapper/ant-1.6.5.pom.json diff --git a/discovery/tests/testfiles/maven/mapper/axis-1.4.pom b/minecode/tests/testfiles/maven/mapper/axis-1.4.pom similarity index 100% rename from discovery/tests/testfiles/maven/mapper/axis-1.4.pom rename to minecode/tests/testfiles/maven/mapper/axis-1.4.pom diff --git a/discovery/tests/testfiles/maven/mapper/axis-1.4.pom.package.json b/minecode/tests/testfiles/maven/mapper/axis-1.4.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/mapper/axis-1.4.pom.package.json rename to minecode/tests/testfiles/maven/mapper/axis-1.4.pom.package.json diff --git a/discovery/tests/testfiles/maven/mapper/commons-jaxrs-1.21.pom b/minecode/tests/testfiles/maven/mapper/commons-jaxrs-1.21.pom similarity index 100% rename from discovery/tests/testfiles/maven/mapper/commons-jaxrs-1.21.pom rename to minecode/tests/testfiles/maven/mapper/commons-jaxrs-1.21.pom diff --git a/discovery/tests/testfiles/maven/mapper/commons-jaxrs-1.21.pom.package.json b/minecode/tests/testfiles/maven/mapper/commons-jaxrs-1.21.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/mapper/commons-jaxrs-1.21.pom.package.json rename to minecode/tests/testfiles/maven/mapper/commons-jaxrs-1.21.pom.package.json diff --git a/discovery/tests/testfiles/maven/mapper/commons-pool-1.5.7.pom b/minecode/tests/testfiles/maven/mapper/commons-pool-1.5.7.pom similarity index 100% rename from discovery/tests/testfiles/maven/mapper/commons-pool-1.5.7.pom rename to minecode/tests/testfiles/maven/mapper/commons-pool-1.5.7.pom diff --git a/discovery/tests/testfiles/maven/mapper/commons-pool-1.5.7.pom.package.json b/minecode/tests/testfiles/maven/mapper/commons-pool-1.5.7.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/mapper/commons-pool-1.5.7.pom.package.json rename to minecode/tests/testfiles/maven/mapper/commons-pool-1.5.7.pom.package.json diff --git a/discovery/tests/testfiles/maven/mapper/depgraph-view-0.1.pom b/minecode/tests/testfiles/maven/mapper/depgraph-view-0.1.pom similarity index 100% rename from discovery/tests/testfiles/maven/mapper/depgraph-view-0.1.pom rename to minecode/tests/testfiles/maven/mapper/depgraph-view-0.1.pom diff --git a/discovery/tests/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom b/minecode/tests/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom similarity index 100% rename from discovery/tests/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom rename to minecode/tests/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom diff --git a/discovery/tests/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom.package.json b/minecode/tests/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom.package.json rename to minecode/tests/testfiles/maven/mapper/maven-all-1.0-RELEASE.pom.package.json diff --git a/discovery/tests/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom b/minecode/tests/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom similarity index 100% rename from discovery/tests/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom rename to minecode/tests/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom diff --git a/discovery/tests/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom.package.json b/minecode/tests/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom.package.json rename to minecode/tests/testfiles/maven/mapper/mysql-connector-java-5.1.27.pom.package.json diff --git a/discovery/tests/testfiles/maven/mapper/struts-menu-2.4.2.pom b/minecode/tests/testfiles/maven/mapper/struts-menu-2.4.2.pom similarity index 100% rename from discovery/tests/testfiles/maven/mapper/struts-menu-2.4.2.pom rename to minecode/tests/testfiles/maven/mapper/struts-menu-2.4.2.pom diff --git a/discovery/tests/testfiles/maven/mapper/struts-menu-2.4.2.pom.package.json b/minecode/tests/testfiles/maven/mapper/struts-menu-2.4.2.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/mapper/struts-menu-2.4.2.pom.package.json rename to minecode/tests/testfiles/maven/mapper/struts-menu-2.4.2.pom.package.json diff --git a/discovery/tests/testfiles/maven/mapper/xbean-jmx-2.0.pom b/minecode/tests/testfiles/maven/mapper/xbean-jmx-2.0.pom similarity index 100% rename from discovery/tests/testfiles/maven/mapper/xbean-jmx-2.0.pom rename to minecode/tests/testfiles/maven/mapper/xbean-jmx-2.0.pom diff --git a/discovery/tests/testfiles/maven/mapper/xbean-jmx-2.0.pom.package.json b/minecode/tests/testfiles/maven/mapper/xbean-jmx-2.0.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/mapper/xbean-jmx-2.0.pom.package.json rename to minecode/tests/testfiles/maven/mapper/xbean-jmx-2.0.pom.package.json diff --git a/discovery/tests/testfiles/maven/maven-metadata/expected_maven_xml.json b/minecode/tests/testfiles/maven/maven-metadata/expected_maven_xml.json similarity index 100% rename from discovery/tests/testfiles/maven/maven-metadata/expected_maven_xml.json rename to minecode/tests/testfiles/maven/maven-metadata/expected_maven_xml.json diff --git a/discovery/tests/testfiles/maven/maven-metadata/maven-metadata.xml b/minecode/tests/testfiles/maven/maven-metadata/maven-metadata.xml similarity index 100% rename from discovery/tests/testfiles/maven/maven-metadata/maven-metadata.xml rename to minecode/tests/testfiles/maven/maven-metadata/maven-metadata.xml diff --git a/discovery/tests/testfiles/maven/parsing/empty/common-object-1.0.2.pom b/minecode/tests/testfiles/maven/parsing/empty/common-object-1.0.2.pom similarity index 100% rename from discovery/tests/testfiles/maven/parsing/empty/common-object-1.0.2.pom rename to minecode/tests/testfiles/maven/parsing/empty/common-object-1.0.2.pom diff --git a/discovery/tests/testfiles/maven/parsing/empty/common-object-1.0.2.pom.package.json b/minecode/tests/testfiles/maven/parsing/empty/common-object-1.0.2.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/parsing/empty/common-object-1.0.2.pom.package.json rename to minecode/tests/testfiles/maven/parsing/empty/common-object-1.0.2.pom.package.json diff --git a/discovery/tests/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom b/minecode/tests/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom similarity index 100% rename from discovery/tests/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom rename to minecode/tests/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom diff --git a/discovery/tests/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom.package.json b/minecode/tests/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom.package.json rename to minecode/tests/testfiles/maven/parsing/empty/osgl-http-1.1.2.pom.package.json diff --git a/discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom b/minecode/tests/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom similarity index 100% rename from discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom rename to minecode/tests/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom diff --git a/discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom.package.json b/minecode/tests/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom.package.json rename to minecode/tests/testfiles/maven/parsing/loop/argus-webservices-2.7.0.pom.package.json diff --git a/discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom b/minecode/tests/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom similarity index 100% rename from discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom rename to minecode/tests/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom diff --git a/discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom.package.json b/minecode/tests/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom.package.json rename to minecode/tests/testfiles/maven/parsing/loop/argus-webservices-2.8.0.pom.package.json diff --git a/discovery/tests/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom b/minecode/tests/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom similarity index 100% rename from discovery/tests/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom rename to minecode/tests/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom diff --git a/discovery/tests/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom.package.json b/minecode/tests/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom.package.json rename to minecode/tests/testfiles/maven/parsing/loop/coreplugin-1.0.0.pom.package.json diff --git a/discovery/tests/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom b/minecode/tests/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom similarity index 100% rename from discovery/tests/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom rename to minecode/tests/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom diff --git a/discovery/tests/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom.package.json b/minecode/tests/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom.package.json rename to minecode/tests/testfiles/maven/parsing/loop/jacuzzi-annotations-0.2.1.pom.package.json diff --git a/discovery/tests/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom b/minecode/tests/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom similarity index 100% rename from discovery/tests/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom rename to minecode/tests/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom diff --git a/discovery/tests/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom.package.json b/minecode/tests/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom.package.json rename to minecode/tests/testfiles/maven/parsing/loop/jacuzzi-database-0.2.1.pom.package.json diff --git a/discovery/tests/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom b/minecode/tests/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom similarity index 100% rename from discovery/tests/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom rename to minecode/tests/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom diff --git a/discovery/tests/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom.package.json b/minecode/tests/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom.package.json rename to minecode/tests/testfiles/maven/parsing/loop/ojcms-beans-0.1-beta.pom.package.json diff --git a/discovery/tests/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom b/minecode/tests/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom similarity index 100% rename from discovery/tests/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom rename to minecode/tests/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom diff --git a/discovery/tests/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom.package.json b/minecode/tests/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom.package.json rename to minecode/tests/testfiles/maven/parsing/loop/pkg-2.0.13.1005.pom.package.json diff --git a/discovery/tests/testfiles/maven/parsing/parse/jds-2.17.0718b.pom b/minecode/tests/testfiles/maven/parsing/parse/jds-2.17.0718b.pom similarity index 100% rename from discovery/tests/testfiles/maven/parsing/parse/jds-2.17.0718b.pom rename to minecode/tests/testfiles/maven/parsing/parse/jds-2.17.0718b.pom diff --git a/discovery/tests/testfiles/maven/parsing/parse/jds-2.17.0718b.pom.package.json b/minecode/tests/testfiles/maven/parsing/parse/jds-2.17.0718b.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/parsing/parse/jds-2.17.0718b.pom.package.json rename to minecode/tests/testfiles/maven/parsing/parse/jds-2.17.0718b.pom.package.json diff --git a/discovery/tests/testfiles/maven/parsing/parse/jds-3.0.1.pom b/minecode/tests/testfiles/maven/parsing/parse/jds-3.0.1.pom similarity index 100% rename from discovery/tests/testfiles/maven/parsing/parse/jds-3.0.1.pom rename to minecode/tests/testfiles/maven/parsing/parse/jds-3.0.1.pom diff --git a/discovery/tests/testfiles/maven/parsing/parse/jds-3.0.1.pom.package.json b/minecode/tests/testfiles/maven/parsing/parse/jds-3.0.1.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/parsing/parse/jds-3.0.1.pom.package.json rename to minecode/tests/testfiles/maven/parsing/parse/jds-3.0.1.pom.package.json diff --git a/discovery/tests/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom b/minecode/tests/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom similarity index 100% rename from discovery/tests/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom rename to minecode/tests/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom diff --git a/discovery/tests/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom.package.json b/minecode/tests/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom.package.json rename to minecode/tests/testfiles/maven/parsing/parse/maven-javanet-plugin-1.7.pom.package.json diff --git a/discovery/tests/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom b/minecode/tests/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom similarity index 100% rename from discovery/tests/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom rename to minecode/tests/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom diff --git a/discovery/tests/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom.package.json b/minecode/tests/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom.package.json similarity index 100% rename from discovery/tests/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom.package.json rename to minecode/tests/testfiles/maven/parsing/parse/springmvc-rest-docs-maven-plugin-1.0-RC1.pom.package.json diff --git a/discovery/tests/testfiles/maven/pom/classworlds-1.1-alpha-2.pom b/minecode/tests/testfiles/maven/pom/classworlds-1.1-alpha-2.pom similarity index 100% rename from discovery/tests/testfiles/maven/pom/classworlds-1.1-alpha-2.pom rename to minecode/tests/testfiles/maven/pom/classworlds-1.1-alpha-2.pom diff --git a/discovery/tests/testfiles/npm/0flux.json b/minecode/tests/testfiles/npm/0flux.json similarity index 100% rename from discovery/tests/testfiles/npm/0flux.json rename to minecode/tests/testfiles/npm/0flux.json diff --git a/discovery/tests/testfiles/npm/0flux_npm_expected.json b/minecode/tests/testfiles/npm/0flux_npm_expected.json similarity index 100% rename from discovery/tests/testfiles/npm/0flux_npm_expected.json rename to minecode/tests/testfiles/npm/0flux_npm_expected.json diff --git a/discovery/tests/testfiles/npm/1000_records.json b/minecode/tests/testfiles/npm/1000_records.json similarity index 100% rename from discovery/tests/testfiles/npm/1000_records.json rename to minecode/tests/testfiles/npm/1000_records.json diff --git a/discovery/tests/testfiles/npm/2112.json b/minecode/tests/testfiles/npm/2112.json similarity index 100% rename from discovery/tests/testfiles/npm/2112.json rename to minecode/tests/testfiles/npm/2112.json diff --git a/discovery/tests/testfiles/npm/29_record_expected.json b/minecode/tests/testfiles/npm/29_record_expected.json similarity index 100% rename from discovery/tests/testfiles/npm/29_record_expected.json rename to minecode/tests/testfiles/npm/29_record_expected.json diff --git a/discovery/tests/testfiles/npm/554_record_expected.json b/minecode/tests/testfiles/npm/554_record_expected.json similarity index 100% rename from discovery/tests/testfiles/npm/554_record_expected.json rename to minecode/tests/testfiles/npm/554_record_expected.json diff --git a/discovery/tests/testfiles/npm/expected_1000_records.json b/minecode/tests/testfiles/npm/expected_1000_records.json similarity index 100% rename from discovery/tests/testfiles/npm/expected_1000_records.json rename to minecode/tests/testfiles/npm/expected_1000_records.json diff --git a/discovery/tests/testfiles/npm/expected_doclimit_visitor.json b/minecode/tests/testfiles/npm/expected_doclimit_visitor.json similarity index 100% rename from discovery/tests/testfiles/npm/expected_doclimit_visitor.json rename to minecode/tests/testfiles/npm/expected_doclimit_visitor.json diff --git a/discovery/tests/testfiles/npm/expected_npmdownload_data_vistor.json b/minecode/tests/testfiles/npm/expected_npmdownload_data_vistor.json similarity index 100% rename from discovery/tests/testfiles/npm/expected_npmdownload_data_vistor.json rename to minecode/tests/testfiles/npm/expected_npmdownload_data_vistor.json diff --git a/discovery/tests/testfiles/npm/expected_npmdownloadvistor.json b/minecode/tests/testfiles/npm/expected_npmdownloadvistor.json similarity index 100% rename from discovery/tests/testfiles/npm/expected_npmdownloadvistor.json rename to minecode/tests/testfiles/npm/expected_npmdownloadvistor.json diff --git a/discovery/tests/testfiles/npm/expected_npmindexvisitor.json b/minecode/tests/testfiles/npm/expected_npmindexvisitor.json similarity index 100% rename from discovery/tests/testfiles/npm/expected_npmindexvisitor.json rename to minecode/tests/testfiles/npm/expected_npmindexvisitor.json diff --git a/discovery/tests/testfiles/npm/expected_over_limit.json b/minecode/tests/testfiles/npm/expected_over_limit.json similarity index 100% rename from discovery/tests/testfiles/npm/expected_over_limit.json rename to minecode/tests/testfiles/npm/expected_over_limit.json diff --git a/discovery/tests/testfiles/npm/expected_ticket_439.json b/minecode/tests/testfiles/npm/expected_ticket_439.json similarity index 100% rename from discovery/tests/testfiles/npm/expected_ticket_439.json rename to minecode/tests/testfiles/npm/expected_ticket_439.json diff --git a/discovery/tests/testfiles/npm/expected_ticket_440.json b/minecode/tests/testfiles/npm/expected_ticket_440.json similarity index 100% rename from discovery/tests/testfiles/npm/expected_ticket_440.json rename to minecode/tests/testfiles/npm/expected_ticket_440.json diff --git a/discovery/tests/testfiles/npm/grunticon-sass.json b/minecode/tests/testfiles/npm/grunticon-sass.json similarity index 100% rename from discovery/tests/testfiles/npm/grunticon-sass.json rename to minecode/tests/testfiles/npm/grunticon-sass.json diff --git a/discovery/tests/testfiles/npm/jsonp-filter-expected.json b/minecode/tests/testfiles/npm/jsonp-filter-expected.json similarity index 100% rename from discovery/tests/testfiles/npm/jsonp-filter-expected.json rename to minecode/tests/testfiles/npm/jsonp-filter-expected.json diff --git a/discovery/tests/testfiles/npm/jsonp-filter.json b/minecode/tests/testfiles/npm/jsonp-filter.json similarity index 100% rename from discovery/tests/testfiles/npm/jsonp-filter.json rename to minecode/tests/testfiles/npm/jsonp-filter.json diff --git a/discovery/tests/testfiles/npm/mapper/index.expected.json b/minecode/tests/testfiles/npm/mapper/index.expected.json similarity index 100% rename from discovery/tests/testfiles/npm/mapper/index.expected.json rename to minecode/tests/testfiles/npm/mapper/index.expected.json diff --git a/discovery/tests/testfiles/npm/mapper/index.json b/minecode/tests/testfiles/npm/mapper/index.json similarity index 100% rename from discovery/tests/testfiles/npm/mapper/index.json rename to minecode/tests/testfiles/npm/mapper/index.json diff --git a/discovery/tests/testfiles/npm/microdata-node_expected.json b/minecode/tests/testfiles/npm/microdata-node_expected.json similarity index 100% rename from discovery/tests/testfiles/npm/microdata-node_expected.json rename to minecode/tests/testfiles/npm/microdata-node_expected.json diff --git a/discovery/tests/testfiles/npm/microdata.json b/minecode/tests/testfiles/npm/microdata.json similarity index 100% rename from discovery/tests/testfiles/npm/microdata.json rename to minecode/tests/testfiles/npm/microdata.json diff --git a/discovery/tests/testfiles/npm/npm_2112_expected.json b/minecode/tests/testfiles/npm/npm_2112_expected.json similarity index 100% rename from discovery/tests/testfiles/npm/npm_2112_expected.json rename to minecode/tests/testfiles/npm/npm_2112_expected.json diff --git a/discovery/tests/testfiles/npm/over_limit.json b/minecode/tests/testfiles/npm/over_limit.json similarity index 100% rename from discovery/tests/testfiles/npm/over_limit.json rename to minecode/tests/testfiles/npm/over_limit.json diff --git a/discovery/tests/testfiles/npm/replicate_doc1.json b/minecode/tests/testfiles/npm/replicate_doc1.json similarity index 100% rename from discovery/tests/testfiles/npm/replicate_doc1.json rename to minecode/tests/testfiles/npm/replicate_doc1.json diff --git a/discovery/tests/testfiles/npm/ticket_439.json b/minecode/tests/testfiles/npm/ticket_439.json similarity index 100% rename from discovery/tests/testfiles/npm/ticket_439.json rename to minecode/tests/testfiles/npm/ticket_439.json diff --git a/discovery/tests/testfiles/npm/ticket_440_records.json b/minecode/tests/testfiles/npm/ticket_440_records.json similarity index 100% rename from discovery/tests/testfiles/npm/ticket_440_records.json rename to minecode/tests/testfiles/npm/ticket_440_records.json diff --git a/discovery/tests/testfiles/pypi/boolean.py-2.0.dev3.json b/minecode/tests/testfiles/pypi/boolean.py-2.0.dev3.json similarity index 100% rename from discovery/tests/testfiles/pypi/boolean.py-2.0.dev3.json rename to minecode/tests/testfiles/pypi/boolean.py-2.0.dev3.json diff --git a/discovery/tests/testfiles/pypi/boolean.py.json b/minecode/tests/testfiles/pypi/boolean.py.json similarity index 100% rename from discovery/tests/testfiles/pypi/boolean.py.json rename to minecode/tests/testfiles/pypi/boolean.py.json diff --git a/discovery/tests/testfiles/pypi/cage.json b/minecode/tests/testfiles/pypi/cage.json similarity index 100% rename from discovery/tests/testfiles/pypi/cage.json rename to minecode/tests/testfiles/pypi/cage.json diff --git a/discovery/tests/testfiles/pypi/cage_1.1.2.json b/minecode/tests/testfiles/pypi/cage_1.1.2.json similarity index 100% rename from discovery/tests/testfiles/pypi/cage_1.1.2.json rename to minecode/tests/testfiles/pypi/cage_1.1.2.json diff --git a/discovery/tests/testfiles/pypi/cage_1.1.3.json b/minecode/tests/testfiles/pypi/cage_1.1.3.json similarity index 100% rename from discovery/tests/testfiles/pypi/cage_1.1.3.json rename to minecode/tests/testfiles/pypi/cage_1.1.3.json diff --git a/discovery/tests/testfiles/pypi/expected-CAGE-1.1.2.json b/minecode/tests/testfiles/pypi/expected-CAGE-1.1.2.json similarity index 100% rename from discovery/tests/testfiles/pypi/expected-CAGE-1.1.2.json rename to minecode/tests/testfiles/pypi/expected-CAGE-1.1.2.json diff --git a/discovery/tests/testfiles/pypi/expected-CAGE-1.1.3.json b/minecode/tests/testfiles/pypi/expected-CAGE-1.1.3.json similarity index 100% rename from discovery/tests/testfiles/pypi/expected-CAGE-1.1.3.json rename to minecode/tests/testfiles/pypi/expected-CAGE-1.1.3.json diff --git a/discovery/tests/testfiles/pypi/expected-boolean.py-2.0.dev3.json b/minecode/tests/testfiles/pypi/expected-boolean.py-2.0.dev3.json similarity index 100% rename from discovery/tests/testfiles/pypi/expected-boolean.py-2.0.dev3.json rename to minecode/tests/testfiles/pypi/expected-boolean.py-2.0.dev3.json diff --git a/discovery/tests/testfiles/pypi/expected-lxml-3.2.0.json b/minecode/tests/testfiles/pypi/expected-lxml-3.2.0.json similarity index 100% rename from discovery/tests/testfiles/pypi/expected-lxml-3.2.0.json rename to minecode/tests/testfiles/pypi/expected-lxml-3.2.0.json diff --git a/discovery/tests/testfiles/pypi/expected_data-boolean.py-2.0.dev3.json b/minecode/tests/testfiles/pypi/expected_data-boolean.py-2.0.dev3.json similarity index 100% rename from discovery/tests/testfiles/pypi/expected_data-boolean.py-2.0.dev3.json rename to minecode/tests/testfiles/pypi/expected_data-boolean.py-2.0.dev3.json diff --git a/discovery/tests/testfiles/pypi/expected_data-cage_1.1.2.json b/minecode/tests/testfiles/pypi/expected_data-cage_1.1.2.json similarity index 100% rename from discovery/tests/testfiles/pypi/expected_data-cage_1.1.2.json rename to minecode/tests/testfiles/pypi/expected_data-cage_1.1.2.json diff --git a/discovery/tests/testfiles/pypi/expected_data-cage_1.1.3.json b/minecode/tests/testfiles/pypi/expected_data-cage_1.1.3.json similarity index 100% rename from discovery/tests/testfiles/pypi/expected_data-cage_1.1.3.json rename to minecode/tests/testfiles/pypi/expected_data-cage_1.1.3.json diff --git a/discovery/tests/testfiles/pypi/expected_uri_visitor1.json b/minecode/tests/testfiles/pypi/expected_uri_visitor1.json similarity index 100% rename from discovery/tests/testfiles/pypi/expected_uri_visitor1.json rename to minecode/tests/testfiles/pypi/expected_uri_visitor1.json diff --git a/discovery/tests/testfiles/pypi/expected_uri_visitor2.json b/minecode/tests/testfiles/pypi/expected_uri_visitor2.json similarity index 100% rename from discovery/tests/testfiles/pypi/expected_uri_visitor2.json rename to minecode/tests/testfiles/pypi/expected_uri_visitor2.json diff --git a/discovery/tests/testfiles/pypi/expected_uris-boolean.py-2.0.dev3.json b/minecode/tests/testfiles/pypi/expected_uris-boolean.py-2.0.dev3.json similarity index 100% rename from discovery/tests/testfiles/pypi/expected_uris-boolean.py-2.0.dev3.json rename to minecode/tests/testfiles/pypi/expected_uris-boolean.py-2.0.dev3.json diff --git a/discovery/tests/testfiles/pypi/expected_uris-boolean.py.json b/minecode/tests/testfiles/pypi/expected_uris-boolean.py.json similarity index 100% rename from discovery/tests/testfiles/pypi/expected_uris-boolean.py.json rename to minecode/tests/testfiles/pypi/expected_uris-boolean.py.json diff --git a/discovery/tests/testfiles/pypi/expected_uris-cage.json b/minecode/tests/testfiles/pypi/expected_uris-cage.json similarity index 100% rename from discovery/tests/testfiles/pypi/expected_uris-cage.json rename to minecode/tests/testfiles/pypi/expected_uris-cage.json diff --git a/discovery/tests/testfiles/pypi/expected_uris-cage_1.1.2.json b/minecode/tests/testfiles/pypi/expected_uris-cage_1.1.2.json similarity index 100% rename from discovery/tests/testfiles/pypi/expected_uris-cage_1.1.2.json rename to minecode/tests/testfiles/pypi/expected_uris-cage_1.1.2.json diff --git a/discovery/tests/testfiles/pypi/expected_uris-cage_1.1.3.json b/minecode/tests/testfiles/pypi/expected_uris-cage_1.1.3.json similarity index 100% rename from discovery/tests/testfiles/pypi/expected_uris-cage_1.1.3.json rename to minecode/tests/testfiles/pypi/expected_uris-cage_1.1.3.json diff --git a/discovery/tests/testfiles/pypi/lxml-3.2.0.json b/minecode/tests/testfiles/pypi/lxml-3.2.0.json similarity index 100% rename from discovery/tests/testfiles/pypi/lxml-3.2.0.json rename to minecode/tests/testfiles/pypi/lxml-3.2.0.json diff --git a/discovery/tests/testfiles/pypi/map/3to2-1.1.1.json b/minecode/tests/testfiles/pypi/map/3to2-1.1.1.json similarity index 100% rename from discovery/tests/testfiles/pypi/map/3to2-1.1.1.json rename to minecode/tests/testfiles/pypi/map/3to2-1.1.1.json diff --git a/discovery/tests/testfiles/pypi/map/expected-3to2-1.1.1.json b/minecode/tests/testfiles/pypi/map/expected-3to2-1.1.1.json similarity index 100% rename from discovery/tests/testfiles/pypi/map/expected-3to2-1.1.1.json rename to minecode/tests/testfiles/pypi/map/expected-3to2-1.1.1.json diff --git a/discovery/tests/testfiles/pypi/pypiindexvisitor-expected.json b/minecode/tests/testfiles/pypi/pypiindexvisitor-expected.json similarity index 100% rename from discovery/tests/testfiles/pypi/pypiindexvisitor-expected.json rename to minecode/tests/testfiles/pypi/pypiindexvisitor-expected.json diff --git a/discovery/tests/testfiles/rsync/rsync_dev.dir b/minecode/tests/testfiles/rsync/rsync_dev.dir similarity index 100% rename from discovery/tests/testfiles/rsync/rsync_dev.dir rename to minecode/tests/testfiles/rsync/rsync_dev.dir diff --git a/discovery/tests/testfiles/rsync/rsync_dir/bar/that/baz b/minecode/tests/testfiles/rsync/rsync_dir/bar/that/baz similarity index 100% rename from discovery/tests/testfiles/rsync/rsync_dir/bar/that/baz rename to minecode/tests/testfiles/rsync/rsync_dir/bar/that/baz diff --git a/discovery/tests/testfiles/rsync/rsync_dir/bar/this b/minecode/tests/testfiles/rsync/rsync_dir/bar/this similarity index 100% rename from discovery/tests/testfiles/rsync/rsync_dir/bar/this rename to minecode/tests/testfiles/rsync/rsync_dir/bar/this diff --git a/discovery/tests/testfiles/rsync/rsync_dir/foo b/minecode/tests/testfiles/rsync/rsync_dir/foo similarity index 100% rename from discovery/tests/testfiles/rsync/rsync_dir/foo rename to minecode/tests/testfiles/rsync/rsync_dir/foo diff --git a/discovery/tests/testfiles/rsync/rsync_modules b/minecode/tests/testfiles/rsync/rsync_modules similarity index 100% rename from discovery/tests/testfiles/rsync/rsync_modules rename to minecode/tests/testfiles/rsync/rsync_modules diff --git a/discovery/tests/testfiles/rsync/rsync_v3.0.9_protocol30.dir b/minecode/tests/testfiles/rsync/rsync_v3.0.9_protocol30.dir similarity index 100% rename from discovery/tests/testfiles/rsync/rsync_v3.0.9_protocol30.dir rename to minecode/tests/testfiles/rsync/rsync_v3.0.9_protocol30.dir diff --git a/discovery/tests/testfiles/rsync/rsync_v3.1.0_protocol31.dir b/minecode/tests/testfiles/rsync/rsync_v3.1.0_protocol31.dir similarity index 100% rename from discovery/tests/testfiles/rsync/rsync_v3.1.0_protocol31.dir rename to minecode/tests/testfiles/rsync/rsync_v3.1.0_protocol31.dir diff --git a/discovery/tests/testfiles/rsync/rsync_wicket.dir b/minecode/tests/testfiles/rsync/rsync_wicket.dir similarity index 100% rename from discovery/tests/testfiles/rsync/rsync_wicket.dir rename to minecode/tests/testfiles/rsync/rsync_wicket.dir diff --git a/discovery/tests/testfiles/rubygems/0mq-0.4.1.gem.metadata b/minecode/tests/testfiles/rubygems/0mq-0.4.1.gem.metadata similarity index 100% rename from discovery/tests/testfiles/rubygems/0mq-0.4.1.gem.metadata rename to minecode/tests/testfiles/rubygems/0mq-0.4.1.gem.metadata diff --git a/discovery/tests/testfiles/rubygems/0mq-0.4.1.gem.package.json b/minecode/tests/testfiles/rubygems/0mq-0.4.1.gem.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/0mq-0.4.1.gem.package.json rename to minecode/tests/testfiles/rubygems/0mq-0.4.1.gem.package.json diff --git a/discovery/tests/testfiles/rubygems/a_okay-0.1.0.gem b/minecode/tests/testfiles/rubygems/a_okay-0.1.0.gem similarity index 100% rename from discovery/tests/testfiles/rubygems/a_okay-0.1.0.gem rename to minecode/tests/testfiles/rubygems/a_okay-0.1.0.gem diff --git a/discovery/tests/testfiles/rubygems/a_okay-0.1.0.gem.metadata b/minecode/tests/testfiles/rubygems/a_okay-0.1.0.gem.metadata similarity index 100% rename from discovery/tests/testfiles/rubygems/a_okay-0.1.0.gem.metadata rename to minecode/tests/testfiles/rubygems/a_okay-0.1.0.gem.metadata diff --git a/discovery/tests/testfiles/rubygems/a_okay-0.1.0.gem.package.json b/minecode/tests/testfiles/rubygems/a_okay-0.1.0.gem.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/a_okay-0.1.0.gem.package.json rename to minecode/tests/testfiles/rubygems/a_okay-0.1.0.gem.package.json diff --git a/discovery/tests/testfiles/rubygems/action_tracker-1.0.2.gem b/minecode/tests/testfiles/rubygems/action_tracker-1.0.2.gem similarity index 100% rename from discovery/tests/testfiles/rubygems/action_tracker-1.0.2.gem rename to minecode/tests/testfiles/rubygems/action_tracker-1.0.2.gem diff --git a/discovery/tests/testfiles/rubygems/action_tracker-1.0.2.gem.package.json b/minecode/tests/testfiles/rubygems/action_tracker-1.0.2.gem.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/action_tracker-1.0.2.gem.package.json rename to minecode/tests/testfiles/rubygems/action_tracker-1.0.2.gem.package.json diff --git a/discovery/tests/testfiles/rubygems/apiv1/0xffffff.api.json b/minecode/tests/testfiles/rubygems/apiv1/0xffffff.api.json similarity index 100% rename from discovery/tests/testfiles/rubygems/apiv1/0xffffff.api.json rename to minecode/tests/testfiles/rubygems/apiv1/0xffffff.api.json diff --git a/discovery/tests/testfiles/rubygems/apiv1/0xffffff.api.package.json b/minecode/tests/testfiles/rubygems/apiv1/0xffffff.api.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/apiv1/0xffffff.api.package.json rename to minecode/tests/testfiles/rubygems/apiv1/0xffffff.api.package.json diff --git a/discovery/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.json b/minecode/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.json similarity index 100% rename from discovery/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.json rename to minecode/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.json diff --git a/discovery/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.mapped.json b/minecode/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.mapped.json similarity index 100% rename from discovery/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.mapped.json rename to minecode/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.mapped.json diff --git a/discovery/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.package.json b/minecode/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.package.json rename to minecode/tests/testfiles/rubygems/apiv1/a1630ty_a1630ty.api.package.json diff --git a/discovery/tests/testfiles/rubygems/apiv1/action_tracker.api.json b/minecode/tests/testfiles/rubygems/apiv1/action_tracker.api.json similarity index 100% rename from discovery/tests/testfiles/rubygems/apiv1/action_tracker.api.json rename to minecode/tests/testfiles/rubygems/apiv1/action_tracker.api.json diff --git a/discovery/tests/testfiles/rubygems/apiv1/action_tracker.api.package.json b/minecode/tests/testfiles/rubygems/apiv1/action_tracker.api.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/apiv1/action_tracker.api.package.json rename to minecode/tests/testfiles/rubygems/apiv1/action_tracker.api.package.json diff --git a/discovery/tests/testfiles/rubygems/apiv1/expected_0xffffff.api.json b/minecode/tests/testfiles/rubygems/apiv1/expected_0xffffff.api.json similarity index 100% rename from discovery/tests/testfiles/rubygems/apiv1/expected_0xffffff.api.json rename to minecode/tests/testfiles/rubygems/apiv1/expected_0xffffff.api.json diff --git a/discovery/tests/testfiles/rubygems/apiv1/expected_a1630ty_a1630ty.api.json b/minecode/tests/testfiles/rubygems/apiv1/expected_a1630ty_a1630ty.api.json similarity index 100% rename from discovery/tests/testfiles/rubygems/apiv1/expected_a1630ty_a1630ty.api.json rename to minecode/tests/testfiles/rubygems/apiv1/expected_a1630ty_a1630ty.api.json diff --git a/discovery/tests/testfiles/rubygems/apiv1/expected_zuck.api.json b/minecode/tests/testfiles/rubygems/apiv1/expected_zuck.api.json similarity index 100% rename from discovery/tests/testfiles/rubygems/apiv1/expected_zuck.api.json rename to minecode/tests/testfiles/rubygems/apiv1/expected_zuck.api.json diff --git a/discovery/tests/testfiles/rubygems/apiv1/zuck.api.json b/minecode/tests/testfiles/rubygems/apiv1/zuck.api.json similarity index 100% rename from discovery/tests/testfiles/rubygems/apiv1/zuck.api.json rename to minecode/tests/testfiles/rubygems/apiv1/zuck.api.json diff --git a/discovery/tests/testfiles/rubygems/apiv1/zuck.api.package.json b/minecode/tests/testfiles/rubygems/apiv1/zuck.api.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/apiv1/zuck.api.package.json rename to minecode/tests/testfiles/rubygems/apiv1/zuck.api.package.json diff --git a/discovery/tests/testfiles/rubygems/archive-tar-minitar-0.5.2.gem b/minecode/tests/testfiles/rubygems/archive-tar-minitar-0.5.2.gem similarity index 100% rename from discovery/tests/testfiles/rubygems/archive-tar-minitar-0.5.2.gem rename to minecode/tests/testfiles/rubygems/archive-tar-minitar-0.5.2.gem diff --git a/discovery/tests/testfiles/rubygems/archive-tar-minitar-0.5.2.gem.package.json b/minecode/tests/testfiles/rubygems/archive-tar-minitar-0.5.2.gem.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/archive-tar-minitar-0.5.2.gem.package.json rename to minecode/tests/testfiles/rubygems/archive-tar-minitar-0.5.2.gem.package.json diff --git a/discovery/tests/testfiles/rubygems/blankslate-3.1.3.gem b/minecode/tests/testfiles/rubygems/blankslate-3.1.3.gem similarity index 100% rename from discovery/tests/testfiles/rubygems/blankslate-3.1.3.gem rename to minecode/tests/testfiles/rubygems/blankslate-3.1.3.gem diff --git a/discovery/tests/testfiles/rubygems/blankslate-3.1.3.gem.package.json b/minecode/tests/testfiles/rubygems/blankslate-3.1.3.gem.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/blankslate-3.1.3.gem.package.json rename to minecode/tests/testfiles/rubygems/blankslate-3.1.3.gem.package.json diff --git a/discovery/tests/testfiles/rubygems/gemspec/address_standardization.gemspec b/minecode/tests/testfiles/rubygems/gemspec/address_standardization.gemspec similarity index 100% rename from discovery/tests/testfiles/rubygems/gemspec/address_standardization.gemspec rename to minecode/tests/testfiles/rubygems/gemspec/address_standardization.gemspec diff --git a/discovery/tests/testfiles/rubygems/gemspec/arel.gemspec b/minecode/tests/testfiles/rubygems/gemspec/arel.gemspec similarity index 100% rename from discovery/tests/testfiles/rubygems/gemspec/arel.gemspec rename to minecode/tests/testfiles/rubygems/gemspec/arel.gemspec diff --git a/discovery/tests/testfiles/rubygems/index/latest_specs.4.8.gz b/minecode/tests/testfiles/rubygems/index/latest_specs.4.8.gz similarity index 100% rename from discovery/tests/testfiles/rubygems/index/latest_specs.4.8.gz rename to minecode/tests/testfiles/rubygems/index/latest_specs.4.8.gz diff --git a/discovery/tests/testfiles/rubygems/index/latest_specs.4.8.gz.expected.json b/minecode/tests/testfiles/rubygems/index/latest_specs.4.8.gz.expected.json similarity index 100% rename from discovery/tests/testfiles/rubygems/index/latest_specs.4.8.gz.expected.json rename to minecode/tests/testfiles/rubygems/index/latest_specs.4.8.gz.expected.json diff --git a/discovery/tests/testfiles/rubygems/m2r-2.1.0.gem b/minecode/tests/testfiles/rubygems/m2r-2.1.0.gem similarity index 100% rename from discovery/tests/testfiles/rubygems/m2r-2.1.0.gem rename to minecode/tests/testfiles/rubygems/m2r-2.1.0.gem diff --git a/discovery/tests/testfiles/rubygems/m2r-2.1.0.gem.package.json b/minecode/tests/testfiles/rubygems/m2r-2.1.0.gem.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/m2r-2.1.0.gem.package.json rename to minecode/tests/testfiles/rubygems/m2r-2.1.0.gem.package.json diff --git a/discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem b/minecode/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem similarity index 100% rename from discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem rename to minecode/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem diff --git a/discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.mapped.json b/minecode/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.mapped.json similarity index 100% rename from discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.mapped.json rename to minecode/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.mapped.json diff --git a/discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.metadata b/minecode/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.metadata similarity index 100% rename from discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.metadata rename to minecode/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.metadata diff --git a/discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.package.json b/minecode/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.package.json rename to minecode/tests/testfiles/rubygems/mysmallidea-address_standardization-0.4.1.gem.package.json diff --git a/discovery/tests/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem b/minecode/tests/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem similarity index 100% rename from discovery/tests/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem rename to minecode/tests/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem diff --git a/discovery/tests/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem.package.json b/minecode/tests/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem.package.json rename to minecode/tests/testfiles/rubygems/mysmallidea-mad_mimi_mailer-0.0.9.gem.package.json diff --git a/discovery/tests/testfiles/rubygems/ng-rails-csrf-0.1.0.gem b/minecode/tests/testfiles/rubygems/ng-rails-csrf-0.1.0.gem similarity index 100% rename from discovery/tests/testfiles/rubygems/ng-rails-csrf-0.1.0.gem rename to minecode/tests/testfiles/rubygems/ng-rails-csrf-0.1.0.gem diff --git a/discovery/tests/testfiles/rubygems/ng-rails-csrf-0.1.0.gem.package.json b/minecode/tests/testfiles/rubygems/ng-rails-csrf-0.1.0.gem.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/ng-rails-csrf-0.1.0.gem.package.json rename to minecode/tests/testfiles/rubygems/ng-rails-csrf-0.1.0.gem.package.json diff --git a/discovery/tests/testfiles/rubygems/small-0.2.gem b/minecode/tests/testfiles/rubygems/small-0.2.gem similarity index 100% rename from discovery/tests/testfiles/rubygems/small-0.2.gem rename to minecode/tests/testfiles/rubygems/small-0.2.gem diff --git a/discovery/tests/testfiles/rubygems/small-0.2.gem.package.json b/minecode/tests/testfiles/rubygems/small-0.2.gem.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/small-0.2.gem.package.json rename to minecode/tests/testfiles/rubygems/small-0.2.gem.package.json diff --git a/discovery/tests/testfiles/rubygems/small_wonder-0.1.10.gem b/minecode/tests/testfiles/rubygems/small_wonder-0.1.10.gem similarity index 100% rename from discovery/tests/testfiles/rubygems/small_wonder-0.1.10.gem rename to minecode/tests/testfiles/rubygems/small_wonder-0.1.10.gem diff --git a/discovery/tests/testfiles/rubygems/small_wonder-0.1.10.gem.package.json b/minecode/tests/testfiles/rubygems/small_wonder-0.1.10.gem.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/small_wonder-0.1.10.gem.package.json rename to minecode/tests/testfiles/rubygems/small_wonder-0.1.10.gem.package.json diff --git a/discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem b/minecode/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem similarity index 100% rename from discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem rename to minecode/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem diff --git a/discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.mapped.json b/minecode/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.mapped.json similarity index 100% rename from discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.mapped.json rename to minecode/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.mapped.json diff --git a/discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.package.json b/minecode/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.package.json similarity index 100% rename from discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.package.json rename to minecode/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.package.json diff --git a/discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.visited.json b/minecode/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.visited.json similarity index 100% rename from discovery/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.visited.json rename to minecode/tests/testfiles/rubygems/sprockets-vendor_gems-0.1.3.gem.visited.json diff --git a/discovery/tests/testfiles/run_map/test_map_uri_does_update_with_same_mining_level-expected.json b/minecode/tests/testfiles/run_map/test_map_uri_does_update_with_same_mining_level-expected.json similarity index 100% rename from discovery/tests/testfiles/run_map/test_map_uri_does_update_with_same_mining_level-expected.json rename to minecode/tests/testfiles/run_map/test_map_uri_does_update_with_same_mining_level-expected.json diff --git a/discovery/tests/testfiles/run_map/test_map_uri_replace_with_new_with_higher_new_mining_level-expected.json b/minecode/tests/testfiles/run_map/test_map_uri_replace_with_new_with_higher_new_mining_level-expected.json similarity index 100% rename from discovery/tests/testfiles/run_map/test_map_uri_replace_with_new_with_higher_new_mining_level-expected.json rename to minecode/tests/testfiles/run_map/test_map_uri_replace_with_new_with_higher_new_mining_level-expected.json diff --git a/discovery/tests/testfiles/run_map/test_map_uri_update_only_empties_with_lesser_new_mining_level-expected.json b/minecode/tests/testfiles/run_map/test_map_uri_update_only_empties_with_lesser_new_mining_level-expected.json similarity index 100% rename from discovery/tests/testfiles/run_map/test_map_uri_update_only_empties_with_lesser_new_mining_level-expected.json rename to minecode/tests/testfiles/run_map/test_map_uri_update_only_empties_with_lesser_new_mining_level-expected.json diff --git a/discovery/tests/testfiles/run_map/test_merge_packages_no_replace-expected.json b/minecode/tests/testfiles/run_map/test_merge_packages_no_replace-expected.json similarity index 100% rename from discovery/tests/testfiles/run_map/test_merge_packages_no_replace-expected.json rename to minecode/tests/testfiles/run_map/test_merge_packages_no_replace-expected.json diff --git a/discovery/tests/testfiles/run_map/test_merge_packages_with_replace-expected.json b/minecode/tests/testfiles/run_map/test_merge_packages_with_replace-expected.json similarity index 100% rename from discovery/tests/testfiles/run_map/test_merge_packages_with_replace-expected.json rename to minecode/tests/testfiles/run_map/test_merge_packages_with_replace-expected.json diff --git a/discovery/tests/testfiles/sourceforge/a-vitkus_profile.html b/minecode/tests/testfiles/sourceforge/a-vitkus_profile.html similarity index 100% rename from discovery/tests/testfiles/sourceforge/a-vitkus_profile.html rename to minecode/tests/testfiles/sourceforge/a-vitkus_profile.html diff --git a/discovery/tests/testfiles/sourceforge/expected_heanet_rsync_dir.json b/minecode/tests/testfiles/sourceforge/expected_heanet_rsync_dir.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/expected_heanet_rsync_dir.json rename to minecode/tests/testfiles/sourceforge/expected_heanet_rsync_dir.json diff --git a/discovery/tests/testfiles/sourceforge/expected_netwiki.json b/minecode/tests/testfiles/sourceforge/expected_netwiki.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/expected_netwiki.json rename to minecode/tests/testfiles/sourceforge/expected_netwiki.json diff --git a/discovery/tests/testfiles/sourceforge/expected_sf_dir_index.json b/minecode/tests/testfiles/sourceforge/expected_sf_dir_index.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/expected_sf_dir_index.json rename to minecode/tests/testfiles/sourceforge/expected_sf_dir_index.json diff --git a/discovery/tests/testfiles/sourceforge/expected_sf_dir_page.json b/minecode/tests/testfiles/sourceforge/expected_sf_dir_page.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/expected_sf_dir_page.json rename to minecode/tests/testfiles/sourceforge/expected_sf_dir_page.json diff --git a/discovery/tests/testfiles/sourceforge/expected_sf_project.json b/minecode/tests/testfiles/sourceforge/expected_sf_project.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/expected_sf_project.json rename to minecode/tests/testfiles/sourceforge/expected_sf_project.json diff --git a/discovery/tests/testfiles/sourceforge/expected_sf_sitemap.json b/minecode/tests/testfiles/sourceforge/expected_sf_sitemap.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/expected_sf_sitemap.json rename to minecode/tests/testfiles/sourceforge/expected_sf_sitemap.json diff --git a/discovery/tests/testfiles/sourceforge/expected_sf_sitemap_new.json b/minecode/tests/testfiles/sourceforge/expected_sf_sitemap_new.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/expected_sf_sitemap_new.json rename to minecode/tests/testfiles/sourceforge/expected_sf_sitemap_new.json diff --git a/discovery/tests/testfiles/sourceforge/expected_sf_sitemap_page.json b/minecode/tests/testfiles/sourceforge/expected_sf_sitemap_page.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/expected_sf_sitemap_page.json rename to minecode/tests/testfiles/sourceforge/expected_sf_sitemap_page.json diff --git a/discovery/tests/testfiles/sourceforge/expected_sf_sitemap_page_new.json b/minecode/tests/testfiles/sourceforge/expected_sf_sitemap_page_new.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/expected_sf_sitemap_page_new.json rename to minecode/tests/testfiles/sourceforge/expected_sf_sitemap_page_new.json diff --git a/discovery/tests/testfiles/sourceforge/expected_sitemap-6.json b/minecode/tests/testfiles/sourceforge/expected_sitemap-6.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/expected_sitemap-6.json rename to minecode/tests/testfiles/sourceforge/expected_sitemap-6.json diff --git a/discovery/tests/testfiles/sourceforge/filezilla.json b/minecode/tests/testfiles/sourceforge/filezilla.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/filezilla.json rename to minecode/tests/testfiles/sourceforge/filezilla.json diff --git a/discovery/tests/testfiles/sourceforge/mapper_niftyphp_expected.json b/minecode/tests/testfiles/sourceforge/mapper_niftyphp_expected.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/mapper_niftyphp_expected.json rename to minecode/tests/testfiles/sourceforge/mapper_niftyphp_expected.json diff --git a/discovery/tests/testfiles/sourceforge/mapper_odanur_expected.json b/minecode/tests/testfiles/sourceforge/mapper_odanur_expected.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/mapper_odanur_expected.json rename to minecode/tests/testfiles/sourceforge/mapper_odanur_expected.json diff --git a/discovery/tests/testfiles/sourceforge/mapper_omonoql_expected.json b/minecode/tests/testfiles/sourceforge/mapper_omonoql_expected.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/mapper_omonoql_expected.json rename to minecode/tests/testfiles/sourceforge/mapper_omonoql_expected.json diff --git a/discovery/tests/testfiles/sourceforge/mapper_openstunts_expected.json b/minecode/tests/testfiles/sourceforge/mapper_openstunts_expected.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/mapper_openstunts_expected.json rename to minecode/tests/testfiles/sourceforge/mapper_openstunts_expected.json diff --git a/discovery/tests/testfiles/sourceforge/monoql.json b/minecode/tests/testfiles/sourceforge/monoql.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/monoql.json rename to minecode/tests/testfiles/sourceforge/monoql.json diff --git a/discovery/tests/testfiles/sourceforge/netwiki.json b/minecode/tests/testfiles/sourceforge/netwiki.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/netwiki.json rename to minecode/tests/testfiles/sourceforge/netwiki.json diff --git a/discovery/tests/testfiles/sourceforge/niftyphp.json b/minecode/tests/testfiles/sourceforge/niftyphp.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/niftyphp.json rename to minecode/tests/testfiles/sourceforge/niftyphp.json diff --git a/discovery/tests/testfiles/sourceforge/odanur.json b/minecode/tests/testfiles/sourceforge/odanur.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/odanur.json rename to minecode/tests/testfiles/sourceforge/odanur.json diff --git a/discovery/tests/testfiles/sourceforge/openstunts.json b/minecode/tests/testfiles/sourceforge/openstunts.json similarity index 100% rename from discovery/tests/testfiles/sourceforge/openstunts.json rename to minecode/tests/testfiles/sourceforge/openstunts.json diff --git a/discovery/tests/testfiles/sourceforge/rsync_heanet_sfnet.dir b/minecode/tests/testfiles/sourceforge/rsync_heanet_sfnet.dir similarity index 100% rename from discovery/tests/testfiles/sourceforge/rsync_heanet_sfnet.dir rename to minecode/tests/testfiles/sourceforge/rsync_heanet_sfnet.dir diff --git a/discovery/tests/testfiles/sourceforge/sitemap-1.xml b/minecode/tests/testfiles/sourceforge/sitemap-1.xml similarity index 100% rename from discovery/tests/testfiles/sourceforge/sitemap-1.xml rename to minecode/tests/testfiles/sourceforge/sitemap-1.xml diff --git a/discovery/tests/testfiles/sourceforge/sitemap-6.xml b/minecode/tests/testfiles/sourceforge/sitemap-6.xml similarity index 100% rename from discovery/tests/testfiles/sourceforge/sitemap-6.xml rename to minecode/tests/testfiles/sourceforge/sitemap-6.xml diff --git a/discovery/tests/testfiles/sourceforge/sitemap.xml b/minecode/tests/testfiles/sourceforge/sitemap.xml similarity index 100% rename from discovery/tests/testfiles/sourceforge/sitemap.xml rename to minecode/tests/testfiles/sourceforge/sitemap.xml diff --git a/discovery/utils.py b/minecode/utils.py similarity index 99% rename from discovery/utils.py rename to minecode/utils.py index c2e88ed0..1bcd023b 100644 --- a/discovery/utils.py +++ b/minecode/utils.py @@ -25,7 +25,7 @@ from commoncode.fileutils import create_dir from extractcode.extract import extract -from discovery.management.commands import get_settings +from minecode.management.commands import get_settings logger = logging.getLogger(__name__) # import sys diff --git a/discovery/utils_test.py b/minecode/utils_test.py similarity index 98% rename from discovery/utils_test.py rename to minecode/utils_test.py index f4afe266..121d821b 100644 --- a/discovery/utils_test.py +++ b/minecode/utils_test.py @@ -23,7 +23,7 @@ from commoncode.testcase import FileBasedTesting from scancode.cli_test_utils import purl_with_fake_uuid -from discovery.utils import get_temp_dir +from minecode.utils import get_temp_dir """ @@ -38,9 +38,6 @@ """ -DISCOVERY_TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'testfiles') - - class BaseMiningTestCase(TestCase): BASE_DIR = os.path.join(os.path.dirname(__file__), 'testfiles') diff --git a/discovery/utils_test.py.ABOUT b/minecode/utils_test.py.ABOUT similarity index 100% rename from discovery/utils_test.py.ABOUT rename to minecode/utils_test.py.ABOUT diff --git a/discovery/utils_test.py.NOTICE b/minecode/utils_test.py.NOTICE similarity index 100% rename from discovery/utils_test.py.NOTICE rename to minecode/utils_test.py.NOTICE diff --git a/discovery/version.py b/minecode/version.py similarity index 100% rename from discovery/version.py rename to minecode/version.py diff --git a/discovery/visitors/__init__.py b/minecode/visitors/__init__.py similarity index 99% rename from discovery/visitors/__init__.py rename to minecode/visitors/__init__.py index 94e0e965..587ded60 100644 --- a/discovery/visitors/__init__.py +++ b/minecode/visitors/__init__.py @@ -15,8 +15,8 @@ import pkgutil import tempfile -from discovery.utils import fetch_http -from discovery.utils import get_temp_file +from minecode.utils import fetch_http +from minecode.utils import get_temp_file # FIXME: use attr or use a plain ResourceURI object insteaad diff --git a/discovery/visitors/debian.py b/minecode/visitors/debian.py similarity index 97% rename from discovery/visitors/debian.py rename to minecode/visitors/debian.py index 6d4fbb9a..94bbe068 100644 --- a/discovery/visitors/debian.py +++ b/minecode/visitors/debian.py @@ -18,12 +18,12 @@ from debian_inspector import copyright as debcopy from packageurl import PackageURL -from discovery import ls -from discovery import seed -from discovery import visit_router -from discovery.visitors import HttpVisitor -from discovery.visitors import NonPersistentHttpVisitor -from discovery.visitors import URI +from minecode import ls +from minecode import seed +from minecode import visit_router +from minecode.visitors import HttpVisitor +from minecode.visitors import NonPersistentHttpVisitor +from minecode.visitors import URI logger = logging.getLogger(__name__) diff --git a/discovery/visitors/fdroid.py b/minecode/visitors/fdroid.py similarity index 90% rename from discovery/visitors/fdroid.py rename to minecode/visitors/fdroid.py index d05776a1..4d171d8c 100644 --- a/discovery/visitors/fdroid.py +++ b/minecode/visitors/fdroid.py @@ -13,13 +13,13 @@ from packageurl import PackageURL -from discovery import seed -from discovery import visit_router -from discovery.utils import get_temp_file -from discovery.visitors import HttpJsonVisitor -from discovery.visitors import URI -from discovery.visitors import Visitor -from discovery.visitors import NonPersistentHttpVisitor +from minecode import seed +from minecode import visit_router +from minecode.utils import get_temp_file +from minecode.visitors import HttpJsonVisitor +from minecode.visitors import URI +from minecode.visitors import Visitor +from minecode.visitors import NonPersistentHttpVisitor """ Visitors for F-Droid package repositories. diff --git a/discovery/visitors/freebsd.py b/minecode/visitors/freebsd.py similarity index 91% rename from discovery/visitors/freebsd.py rename to minecode/visitors/freebsd.py index 90d49be6..fe9e9edf 100644 --- a/discovery/visitors/freebsd.py +++ b/minecode/visitors/freebsd.py @@ -13,12 +13,12 @@ from bs4 import BeautifulSoup -from discovery import seed -from discovery import visit_router -from discovery.utils import extract_file -from discovery.visitors import HttpVisitor -from discovery.visitors import NonPersistentHttpVisitor -from discovery.visitors import URI +from minecode import seed +from minecode import visit_router +from minecode.utils import extract_file +from minecode.visitors import HttpVisitor +from minecode.visitors import NonPersistentHttpVisitor +from minecode.visitors import URI logger = logging.getLogger(__name__) handler = logging.StreamHandler() diff --git a/discovery/visitors/java_stream.LICENSE b/minecode/visitors/java_stream.LICENSE similarity index 100% rename from discovery/visitors/java_stream.LICENSE rename to minecode/visitors/java_stream.LICENSE diff --git a/discovery/visitors/java_stream.py b/minecode/visitors/java_stream.py similarity index 100% rename from discovery/visitors/java_stream.py rename to minecode/visitors/java_stream.py diff --git a/discovery/visitors/java_stream.py.ABOUT b/minecode/visitors/java_stream.py.ABOUT similarity index 100% rename from discovery/visitors/java_stream.py.ABOUT rename to minecode/visitors/java_stream.py.ABOUT diff --git a/discovery/visitors/maven.py b/minecode/visitors/maven.py similarity index 99% rename from discovery/visitors/maven.py rename to minecode/visitors/maven.py index 404b7c96..dd14fd4f 100644 --- a/discovery/visitors/maven.py +++ b/minecode/visitors/maven.py @@ -24,12 +24,12 @@ from packagedcode.maven import build_filename from packagedcode.maven import build_url -from discovery import seed -from discovery import visit_router -from discovery.visitors import java_stream -from discovery.visitors import HttpVisitor -from discovery.visitors import NonPersistentHttpVisitor -from discovery.visitors import URI +from minecode import seed +from minecode import visit_router +from minecode.visitors import java_stream +from minecode.visitors import HttpVisitor +from minecode.visitors import NonPersistentHttpVisitor +from minecode.visitors import URI """ This module handles the Maven repositories such as central and other diff --git a/discovery/visitors/npm.py b/minecode/visitors/npm.py similarity index 95% rename from discovery/visitors/npm.py rename to minecode/visitors/npm.py index a568d25c..afcc342b 100644 --- a/discovery/visitors/npm.py +++ b/minecode/visitors/npm.py @@ -16,10 +16,10 @@ from packagedcode.npm import npm_api_url from packagedcode.npm import split_scoped_package_name -from discovery import seed -from discovery import visit_router -from discovery.visitors import NonPersistentHttpVisitor -from discovery.visitors import URI +from minecode import seed +from minecode import visit_router +from minecode.visitors import NonPersistentHttpVisitor +from minecode.visitors import URI """ diff --git a/discovery/visitors/pypi.py b/minecode/visitors/pypi.py similarity index 95% rename from discovery/visitors/pypi.py rename to minecode/visitors/pypi.py index d4563855..f83b9c1d 100644 --- a/discovery/visitors/pypi.py +++ b/minecode/visitors/pypi.py @@ -14,12 +14,12 @@ from packageurl import PackageURL -from discovery import seed -from discovery import visit_router -from discovery.utils import get_temp_file -from discovery.visitors import HttpJsonVisitor -from discovery.visitors import URI -from discovery.visitors import Visitor +from minecode import seed +from minecode import visit_router +from minecode.utils import get_temp_file +from minecode.visitors import HttpJsonVisitor +from minecode.visitors import URI +from minecode.visitors import Visitor """ diff --git a/discovery/visitors/rubygems.py b/minecode/visitors/rubygems.py similarity index 95% rename from discovery/visitors/rubygems.py rename to minecode/visitors/rubygems.py index a7119cc6..999d21ea 100644 --- a/discovery/visitors/rubygems.py +++ b/minecode/visitors/rubygems.py @@ -18,12 +18,12 @@ from rubymarshal.classes import UsrMarshal from packageurl import PackageURL -from discovery import seed -from discovery import visit_router -from discovery.utils import extract_file -from discovery.visitors import HttpJsonVisitor -from discovery.visitors import NonPersistentHttpVisitor -from discovery.visitors import URI +from minecode import seed +from minecode import visit_router +from minecode.utils import extract_file +from minecode.visitors import HttpJsonVisitor +from minecode.visitors import NonPersistentHttpVisitor +from minecode.visitors import URI logger = logging.getLogger(__name__) diff --git a/discovery/visitors/sourceforge.py b/minecode/visitors/sourceforge.py similarity index 92% rename from discovery/visitors/sourceforge.py rename to minecode/visitors/sourceforge.py index 49a266eb..8ab0d306 100644 --- a/discovery/visitors/sourceforge.py +++ b/minecode/visitors/sourceforge.py @@ -14,12 +14,12 @@ from packageurl import PackageURL -from discovery import seed -from discovery import visit_router -from discovery.visitors import HttpJsonVisitor -from discovery.visitors import HttpVisitor -from discovery.visitors import NonPersistentHttpVisitor -from discovery.visitors import URI +from minecode import seed +from minecode import visit_router +from minecode.visitors import HttpJsonVisitor +from minecode.visitors import HttpVisitor +from minecode.visitors import NonPersistentHttpVisitor +from minecode.visitors import URI logger = logging.getLogger(__name__) diff --git a/purldb/settings.py b/purldb/settings.py index e3925a1e..f953218a 100644 --- a/purldb/settings.py +++ b/purldb/settings.py @@ -55,7 +55,7 @@ # Must come before Third-party apps for proper templates override 'clearcode', 'clearindex', - 'discovery', + 'minecode', 'matchcode', 'packagedb', # Django built-in @@ -270,5 +270,5 @@ # Active seeders: each active seeder class need to be added explictly here ACTIVE_SEEDERS = [ - 'discovery.visitors.npm.NpmSeed', + 'minecode.visitors.npm.NpmSeed', ] From 85b539dff9e768dd02981c4c87a27c96573776b8 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Tue, 20 Dec 2022 10:33:03 -0800 Subject: [PATCH 29/38] Collect all Package Resources in plugin_match * Update code to visit every "page" of Package Resources through the API when creating a PackageInfo object Signed-off-by: Jono Yang --- matchcode/plugin_match.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/matchcode/plugin_match.py b/matchcode/plugin_match.py index 2372c2e1..6f87d58b 100644 --- a/matchcode/plugin_match.py +++ b/matchcode/plugin_match.py @@ -38,9 +38,15 @@ def get_resources_from_packagedb(cls, packagedb_url): if response: package_data = response.json() resources_url = package_data.get('resources') - response = requests.get(resources_url) - print(response.json()) - package_resources.extend(response.json()) + count = 1 + while True: + url = f'{resources_url}?page={count}' + response = requests.get(url) + if response: + package_resources.extend(response.json()) + count += 1 + else: + break return package_resources def create_package_resource_by_paths(self): From f522efb8332e89ec9fdc34e4b89a314e3e1a31a4 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Tue, 20 Dec 2022 17:37:13 -0800 Subject: [PATCH 30/38] Move matching plugin to own project directory Signed-off-by: Jono Yang --- matchcode-toolkit/AUTHORS.rst | 3 + matchcode-toolkit/MANIFEST.in | 15 + matchcode-toolkit/NOTICE | 19 + matchcode-toolkit/README.rst | 59 +++ matchcode-toolkit/apache-2.0.LICENSE | 201 +++++++++ matchcode-toolkit/pyproject.toml | 52 +++ matchcode-toolkit/requirements-dev.txt | 0 matchcode-toolkit/requirements.txt | 0 matchcode-toolkit/setup.cfg | 67 +++ matchcode-toolkit/setup.py | 6 + .../src/matchcode_toolkit/__init__.py | 0 .../src/matchcode_toolkit/fingerprinting.py | 118 ++++++ .../src/matchcode_toolkit/halohash.py | 391 ++++++++++++++++++ .../src/matchcode_toolkit/hash.py | 116 ++++++ .../src/matchcode_toolkit}/plugin_match.py | 17 +- matchcode/api.py | 2 +- matchcode/fingerprinting.py | 10 +- matchcode/models.py | 2 +- matchcode/tests/test_index_packages.py | 2 +- matchcode/tests/test_models.py | 2 +- matchcode/utils.py | 24 +- .../directories/find-ls-expected.json | 2 +- setup.cfg | 4 - 23 files changed, 1077 insertions(+), 35 deletions(-) create mode 100644 matchcode-toolkit/AUTHORS.rst create mode 100644 matchcode-toolkit/MANIFEST.in create mode 100644 matchcode-toolkit/NOTICE create mode 100644 matchcode-toolkit/README.rst create mode 100644 matchcode-toolkit/apache-2.0.LICENSE create mode 100644 matchcode-toolkit/pyproject.toml create mode 100644 matchcode-toolkit/requirements-dev.txt create mode 100644 matchcode-toolkit/requirements.txt create mode 100644 matchcode-toolkit/setup.cfg create mode 100644 matchcode-toolkit/setup.py create mode 100644 matchcode-toolkit/src/matchcode_toolkit/__init__.py create mode 100644 matchcode-toolkit/src/matchcode_toolkit/fingerprinting.py create mode 100644 matchcode-toolkit/src/matchcode_toolkit/halohash.py create mode 100644 matchcode-toolkit/src/matchcode_toolkit/hash.py rename {matchcode => matchcode-toolkit/src/matchcode_toolkit}/plugin_match.py (91%) diff --git a/matchcode-toolkit/AUTHORS.rst b/matchcode-toolkit/AUTHORS.rst new file mode 100644 index 00000000..51a19cc8 --- /dev/null +++ b/matchcode-toolkit/AUTHORS.rst @@ -0,0 +1,3 @@ +The following organizations or individuals have contributed to this repo: + +- diff --git a/matchcode-toolkit/MANIFEST.in b/matchcode-toolkit/MANIFEST.in new file mode 100644 index 00000000..ef3721e8 --- /dev/null +++ b/matchcode-toolkit/MANIFEST.in @@ -0,0 +1,15 @@ +graft src + +include *.LICENSE +include NOTICE +include *.ABOUT +include *.toml +include *.yml +include *.rst +include setup.* +include configure* +include requirements* +include .git* + +global-exclude *.py[co] __pycache__ *.*~ + diff --git a/matchcode-toolkit/NOTICE b/matchcode-toolkit/NOTICE new file mode 100644 index 00000000..65936b2b --- /dev/null +++ b/matchcode-toolkit/NOTICE @@ -0,0 +1,19 @@ +# +# Copyright (c) nexB Inc. and others. +# SPDX-License-Identifier: Apache-2.0 +# +# Visit https://aboutcode.org and https://github.com/nexB/ for support and download. +# ScanCode is a trademark of nexB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/matchcode-toolkit/README.rst b/matchcode-toolkit/README.rst new file mode 100644 index 00000000..be657342 --- /dev/null +++ b/matchcode-toolkit/README.rst @@ -0,0 +1,59 @@ +A Simple Python Project Skeleton +================================ +This repo attempts to standardize the structure of the Python-based project's +repositories using modern Python packaging and configuration techniques. +Using this `blog post`_ as inspiration, this repository serves as the base for +all new Python projects and is mergeable in existing repositories as well. + +.. _blog post: https://blog.jaraco.com/a-project-skeleton-for-python-projects/ + + +Usage +===== + +A brand new project +------------------- +.. code-block:: bash + + git init my-new-repo + cd my-new-repo + git pull git@github.com:nexB/skeleton + + # Create the new repo on GitHub, then update your remote + git remote set-url origin git@github.com:nexB/your-new-repo.git + +From here, you can make the appropriate changes to the files for your specific project. + +Update an existing project +--------------------------- +.. code-block:: bash + + cd my-existing-project + git remote add skeleton git@github.com:nexB/skeleton + git fetch skeleton + git merge skeleton/main --allow-unrelated-histories + +This is also the workflow to use when updating the skeleton files in any given repository. + +More usage instructions can be found in ``docs/skeleton-usage.rst``. + + +Release Notes +============= + +- 2022-03-04: + - Synchronize configure and configure.bat scripts for sanity + - Update CI operating system support with latest Azure OS images + - Streamline utility scripts in etc/scripts/ to create, fetch and manage third-party dependencies + There are now fewer scripts. See etc/scripts/README.rst for details + +- 2021-09-03: + - ``configure`` now requires pinned dependencies via the use of ``requirements.txt`` and ``requirements-dev.txt`` + - ``configure`` can now accept multiple options at once + - Add utility scripts from scancode-toolkit/etc/release/ for use in generating project files + - Rename virtual environment directory from ``tmp`` to ``venv`` + - Update README.rst with instructions for generating ``requirements.txt`` and ``requirements-dev.txt``, + as well as collecting dependencies as wheels and generating ABOUT files for them. + +- 2021-05-11: + - Adopt new configure scripts from ScanCode TK that allows correct configuration of which Python version is used. diff --git a/matchcode-toolkit/apache-2.0.LICENSE b/matchcode-toolkit/apache-2.0.LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/matchcode-toolkit/apache-2.0.LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/matchcode-toolkit/pyproject.toml b/matchcode-toolkit/pyproject.toml new file mode 100644 index 00000000..11534517 --- /dev/null +++ b/matchcode-toolkit/pyproject.toml @@ -0,0 +1,52 @@ +[build-system] +requires = ["setuptools >= 50", "wheel", "setuptools_scm[toml] >= 6"] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +# this is used populated when creating a git archive +# and when there is .git dir and/or there is no git installed +fallback_version = "0.0.1" + +[tool.pytest.ini_options] +norecursedirs = [ + ".git", + "bin", + "dist", + "build", + "_build", + "dist", + "etc", + "local", + "ci", + "docs", + "man", + "share", + "samples", + ".cache", + ".settings", + "Include", + "include", + "Lib", + "lib", + "lib64", + "Lib64", + "Scripts", + "thirdparty", + "tmp", + "venv", + "tests/data", + ".eggs", + "src/*/data", + "tests/*/data" +] + +python_files = "*.py" + +python_classes = "Test" +python_functions = "test" + +addopts = [ + "-rfExXw", + "--strict-markers", + "--doctest-modules" +] diff --git a/matchcode-toolkit/requirements-dev.txt b/matchcode-toolkit/requirements-dev.txt new file mode 100644 index 00000000..e69de29b diff --git a/matchcode-toolkit/requirements.txt b/matchcode-toolkit/requirements.txt new file mode 100644 index 00000000..e69de29b diff --git a/matchcode-toolkit/setup.cfg b/matchcode-toolkit/setup.cfg new file mode 100644 index 00000000..95b018cf --- /dev/null +++ b/matchcode-toolkit/setup.cfg @@ -0,0 +1,67 @@ +[metadata] +name = matchcode-toolkit +version = 0.0.1 +license = Apache-2.0 + +# description must be on ONE line https://github.com/pypa/setuptools/issues/1390 +description = matchcode-toolkit +long_description = file:README.rst +long_description_content_type = text/x-rst +url = https://github.com/nexB/purldb/matchcode-toolkit/ + +author = nexB. Inc. and others +author_email = info@aboutcode.org + +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Topic :: Software Development + Topic :: Utilities + +keywords = + utilities + +license_files = + apache-2.0.LICENSE + NOTICE + AUTHORS.rst + CHANGELOG.rst + +[options] +package_dir = + =src +packages = find: +include_package_data = true +zip_safe = false + +setup_requires = setuptools_scm[toml] >= 4 + +python_requires = >=3.6.* + +install_requires = + bitarray + commoncode + plugincode + +[options.packages.find] +where = src + + +[options.extras_require] +testing = + pytest >= 6, != 7.0.0 + pytest-xdist >= 2 + aboutcode-toolkit >= 6.0.0 + black + +docs = + Sphinx >= 3.3.1 + sphinx-rtd-theme >= 0.5.0 + doc8 >= 0.8.1 + + +[options.entry_points] +scancode_post_scan = + match = matchcode_toolkit.plugin_match:Match diff --git a/matchcode-toolkit/setup.py b/matchcode-toolkit/setup.py new file mode 100644 index 00000000..bac24a43 --- /dev/null +++ b/matchcode-toolkit/setup.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +import setuptools + +if __name__ == "__main__": + setuptools.setup() diff --git a/matchcode-toolkit/src/matchcode_toolkit/__init__.py b/matchcode-toolkit/src/matchcode_toolkit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/matchcode-toolkit/src/matchcode_toolkit/fingerprinting.py b/matchcode-toolkit/src/matchcode_toolkit/fingerprinting.py new file mode 100644 index 00000000..12e3f181 --- /dev/null +++ b/matchcode-toolkit/src/matchcode_toolkit/fingerprinting.py @@ -0,0 +1,118 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +import binascii + +from matchcode_toolkit.halohash import BitAverageHaloHash + + +def _create_directory_fingerprint(inputs): + """ + Return a 128-bit BitAverageHaloHash fingerprint in hex from `inputs` + """ + inputs = [i.encode('utf-8') for i in inputs if i] + bah128 = BitAverageHaloHash(inputs, size_in_bits=128).hexdigest() + inputs_count = len(inputs) + inputs_count_hex_str = '%08x' % inputs_count + bah128 = bah128.decode('utf-8') + directory_fingerprint = inputs_count_hex_str + bah128 + return directory_fingerprint + + +def create_content_fingerprint(resources): + """ + Collect SHA1 strings from a list of Resources (`resources`) and create a + directory fingerprint from them + """ + features = [r.sha1 for r in resources if r.sha1] + return _create_directory_fingerprint(features) + + +def _get_resource_subpath(resource, top): + """ + Return the subpath of `resource` relative to `top` from `codebase` + + For example: + + top.path = 'foo/bar/' + resource.path = 'foo/bar/baz.c' + + The subpath returned would be 'baz.c' + """ + _, _, subpath = resource.path.partition(top.path) + subpath = subpath.lstrip('/') + return subpath + + +def create_structure_fingerprint(directory, children): + """ + Collect the subpaths of children Resources of Resource `directory` and + create a fingerprint from them + """ + features = [] + for child in children: + if not child.path: + continue + child_subpath = _get_resource_subpath(child, directory) + rounded_child_size = int(child.size / 10) * 10 + path_feature = str(rounded_child_size) + child_subpath + features.append(path_feature) + return _create_directory_fingerprint(features) + + +def compute_directory_fingerprints(codebase): + """ + Compute fingerprints for a directory from `codebase` + """ + for resource in codebase.walk(topdown=False): + if resource.is_file or not resource.path: + continue + children = [r for r in resource.walk(codebase) if r.is_file] + if len(children) == 1: + continue + resource.extra_data['directory_content'] = create_content_fingerprint(children) + resource.extra_data['directory_structure'] = create_structure_fingerprint(resource, children) + resource.save(codebase) + return codebase + + +def split_fingerprint(directory_fingerprint): + """ + Given a string `directory_fingerprint`, return the indexed elements count as + an integer and the bah128 fingerprint string + """ + indexed_elements_count_hash = directory_fingerprint[0:8] + indexed_elements_count = int(indexed_elements_count_hash, 16) + bah128 = directory_fingerprint[8:] + return indexed_elements_count, bah128 + + +def hexstring_to_binarray(hex_string): + """ + Convert a hex string to binary form, then store in a bytearray + """ + return bytearray(binascii.unhexlify(hex_string)) + + +def create_halohash_chunks(bah128): + """ + Given a 128-bit bah128 hash string, split it into 4 chunks and return those + chunks as bytearrays + """ + chunk1 = bah128[0:8] + chunk2 = bah128[8:16] + chunk3 = bah128[16:24] + chunk4 = bah128[24:32] + + chunk1 = hexstring_to_binarray(chunk1) + chunk2 = hexstring_to_binarray(chunk2) + chunk3 = hexstring_to_binarray(chunk3) + chunk4 = hexstring_to_binarray(chunk4) + + return chunk1, chunk2, chunk3, chunk4 diff --git a/matchcode-toolkit/src/matchcode_toolkit/halohash.py b/matchcode-toolkit/src/matchcode_toolkit/halohash.py new file mode 100644 index 00000000..61ecf2bf --- /dev/null +++ b/matchcode-toolkit/src/matchcode_toolkit/halohash.py @@ -0,0 +1,391 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +import binascii + +from bitarray import bitarray +from bitarray.util import count_xor + +from commoncode import codec + +from matchcode_toolkit import hash as commoncode_hash + +""" +Halo is a family of hash functions that have the un-common property that mostly +similar -- but not identical -- inputs will hash to very similar outputs. This +type of hash function is sometimes called a locality-sensitive hash function, +because it is sensitive to the locality of the data being hashed. + +The purpose of these hashes is to quickly compare a large number of elements +that are likely to be similar to find candidates and then compute a more +comprehensive similarity only on the candidates. This includes goals such as +identifying near-duplicates of things or to group very similar things together +(a.k.a. clustering), as well as to detect similarities between inputs or perform +quick comparisons under a certain threshold. + +For a traditional 'good' hash function, small changes in the input will yield +very different hash outputs (through diffusion and avalanche effect). For +instance, cryptographic hashes such as SHA1 or MD5 behave this way. If you hash +two bit strings with a SHA1 function and there is only one bit of difference +between these two strings then the resulting hashes will be rather different. On +average, each time one bit is added to the input, good hash functions have half +of the output bits switched from 0 to 1. + +A Halo hash instead hashes similar inputs to the same hash or to a hash that +differs only by a few bits. The similarity between two hashes becomes an +approximation of the similarity between the two original inputs. This simalirity +is computed using the hamming distance or number of non-matching bits between +two hashes outputs bit straings. This hamming distance is roughly proportional +to the similarity between the two original inputs and can be used to estimate +the similarity of inputs without having access to these full input. + +The Halo name is a play on what one of the hashing function does: a halo is like +a fuzzy, halo'ish representation of the input. + +The bit average function ressembles Charikar's algorithm by using each bits in an +array of hashes but does not use a TF/IDF resulting in a simpler procedure. +""" + + +class BitAverageHaloHash(object): + """ + A bit matrix averaging hash. + + The high level processing sketch looks like this: + For an input of: + ['this' ,'is', 'a', 'rose', 'great']: + + * we first hash each list item to get something like + [4, 15, 2, 12, 12] (for instance with a very short hash function of 4 bits output) + + or as bits this would be something like this: + + ['0011', + '1110', + '0010', + '1100', + '1100'] + + * we sum up each bit positions/columns together: + ['0011', + '1110', + '0010', + '1100', + '1100'] + ------- + 3331 + + or stated otherwise: pos1=3, pos2=3, pos3=3, pos4=1 + + * The mean value for a column is number of hashes/2 (2 because we use bits). + Here mean = 5 hashes/2 = 2.5 + + * We compare the sum of each position with the mean and yield a bit: + if pos sum > mean yield 1 else yield 0 + position1 = 3 > mean = 2.5 , then bit=1 + position2 = 3 > mean = 2.5 , then bit=1 + position3 = 3 > mean = 2.5 , then bit=1 + position4 = 1 < mean = 2.5 , then bit=0 + + * We build a hash by concatenating the resulting bits: + pos 1 + pos2 + pos3 + pos4 = '1110' + + In general, this hash seems to show a lower accuracy and higher sensitivity + with small string and small inputs variations than the bucket average hash. + But it works better on shorter inputs. + + Some usage examples: + + >>> z = b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible for intVal'''.split() + >>> a = BitAverageHaloHash(z, size_in_bits=256) + >>> len(a.digest()) + 32 + >>> z = b'''The value specified for size must be no + ... more larger than the smallest bit vector possible for intVal'''.split() + >>> b = BitAverageHaloHash(z, size_in_bits=256) + >>> a.distance(b) + 57 + >>> b.distance(a) + 57 + >>> a = BitAverageHaloHash(size_in_bits=160) + >>> z = [a.update(x) for x in b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible for intVal'''.split()] + >>> assert a.hexdigest() == b'2c10223104c43470e10b1157e6415b2f730057d0' + >>> b = BitAverageHaloHash(size_in_bits=160) + >>> z = [b.update(x) for x in b'''The value specified for size must be no + ... more larger than the smallest bit vector possible for intVal'''.split()] + >>> assert b.hexdigest() == b'2c912433c4c624e0b03b34576641df8fe00017d0' + >>> a.distance(b) + 29 + >>> a = BitAverageHaloHash(size_in_bits=128) + >>> z =[a.update(x) for x in b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible for intVal'''.split()] + >>> assert a.hexdigest() == b'028b1699c0c5310cd1b566a893d12f10' + >>> b = BitAverageHaloHash(size_in_bits=128) + >>> z = [b.update(x) for x in b'''The value specified for size must be no + ... more larger than the smallest bit vector possible for intVal'''.split()] + >>> assert b.hexdigest() == b'0002969060d5b344d1b7602cd9e127b0' + >>> a.distance(b) + 27 + >>> a = BitAverageHaloHash(size_in_bits=64) + >>> z = [a.update(x) for x in b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible for intVal'''.split()] + >>> assert a.hexdigest() == b'028b1699c0c5310c' + >>> b = BitAverageHaloHash(size_in_bits=64) + >>> z = [b.update(x) for x in b'''The value specified for size must be no + ... more larger than the smallest bit vector possible for intVal'''.split()] + >>> assert b.hexdigest() == b'0002969060d5b344' + >>> a.distance(b) + 14 + >>> a = BitAverageHaloHash(size_in_bits=32) + >>> z = [a.update(x) for x in b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible for intVal'''.split()] + >>> b = BitAverageHaloHash(size_in_bits=32) + >>> z = [b.update(x) for x in b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible by intVal'''.split()] + >>> a.distance(b) + 5 + >>> a = BitAverageHaloHash(size_in_bits=512) + >>> z = [a.update(x) for x in b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible for intVal'''.split()] + >>> b = BitAverageHaloHash(size_in_bits=512) + >>> z = [b.update(x) for x in b'''The value specified for size must be at + ... least as large as for the smallest bit vector possible by intVal'''.split()] + >>> a.distance(b) + 46 + """ + + # TODO: Keep state, keep 1 position per column + + # TODO: create method to aggregate multiple BitAverageHaloHashes together + # TODO: refactor this, don't keep all hashes + # TODO: keep only a list of columns + def __init__(self, msg=None, size_in_bits=128): + self.size_in_bits = size_in_bits + self.columns = [0] * size_in_bits + + # TODO: pick one hash module instead of selecting from multiple hash modules + self.hashmodule = lambda x: x + try: + # TODO: pick one hash algorithm + self.hashmodule = commoncode_hash.get_hasher(size_in_bits) + except: + raise Exception('No available hash module for the requested ' + 'hash size in bits: %(size_in_bits)d' % locals()) + self.update(msg) + + @property + def digest_size(self): + return self.size_in_bits // 8 + + def update(self, msg): + """ + Append a bytestring or sequence of bytestrings to the hash. + """ + if not msg: + return + if isinstance(msg, (list, tuple,)): + for m in msg: + self.__hashup(m) + else: + self.__hashup(msg) + + def __hashup(self, msg): + assert isinstance(msg, bytes) + hsh = self.hashmodule(msg).digest() + bits = bitarray_from_bytes(hsh) + normalized = (-1 if v else 1 for v in bits) + for i, column in enumerate(normalized): + self.columns[i] += column + + def hexdigest(self): + """ + Return the hex-encoded hash value. + """ + return binascii.hexlify(self.digest()) + + def b64digest(self): + """ + Return a base64 "url safe"-encoded string representing this hash. + """ + return codec.b64encode(self.digest()) + + def digest(self): + """ + Return a binary string representing this hash. + """ + flattened = [1 if col > 0 else 0 for col in self.columns] + bits = bitarray(flattened) + return bits.tobytes() + + def distance(self, other): + """ + Return the bit Hamming distance between this hash and another hash. + """ + return int(count_xor(self.hash(), other.hash())) + + def hash(self): + return bitarray_from_bytes(self.digest()) + + @classmethod + def combine(cls, hashes): + """ + Return a BitAverageHaloHash by summing and averaging the columns of the + BitAverageHaloHashes in `hashes` together, putting the resulting + columns into a new BitAverageHaloHash and returning it + """ + size_in_bits = hashes[0].size_in_bits + for h in hashes: + assert isinstance(hash, cls), 'all hashes should be a BitAverageHaloHash, not {}'.format(type(h)) + assert h.size_in_bits == size_in_bits + + all_columns = [h.columns for h in hashes] + b = cls() + b.columns = [sum(col) for col in zip(*all_columns)] + return b + + +def bitarray_from_bytes(b): + """ + Return a bitarray built from a byte string b. + """ + a = bitarray() + a.frombytes(b) + return a + + +def byte_hamming_distance(b1, b2): + b1 = binascii.unhexlify(b1) + b2 = binascii.unhexlify(b2) + b1 = bitarray_from_bytes(b1) + b2 = bitarray_from_bytes(b2) + return hamming_distance(b1, b2) + + +def hamming_distance(bv1, bv2): + """ + Return the Hamming distance between `bv1` and `bv2` bitvectors as the + number of equal bits for all positions. (e.g. the count of bits set to one + in an XOR between two bit strings.) + + `bv1` and `bv2` must both be either hash-like Halohash instances (with a + hash() function) or bit array instances (that can be manipulated as-is). + + See http://en.wikipedia.org/wiki/Hamming_distance + + For example: + + >>> b1 = bitarray('0001010111100001111') + >>> b2 = bitarray('0001010111100001111') + >>> hamming_distance(b1, b2) + 0 + >>> b1 = bitarray('11110000') + >>> b2 = bitarray('00001111') + >>> hamming_distance(b1, b2) + 8 + >>> b1 = bitarray('11110000') + >>> b2 = bitarray('00110011') + >>> hamming_distance(b1, b2) + 4 + """ + return int(count_xor(bv1, bv2)) + + +def slices(s, size): + """ + Given a sequence s, return a sequence of non-overlapping slices of `size`. + Raise an AssertionError if the sequence length is not a multiple of `size`. + + For example: + >>> slices([1, 2, 3, 4, 5, 6], 2) + [(1, 2), (3, 4), (5, 6)] + >>> slices([1, 2, 3, 4, 5, 6], 3) + [(1, 2, 3), (4, 5, 6)] + >>> try: + ... slices([1, 2, 3, 4, 5, 6], 4) + ... except AssertionError: + ... pass + """ + length = len(s) + assert length % size == 0, 'Invalid slice size: len(%(s)r) is not a multiple of %(size)r' % locals() + # TODO: time alternative + # return [s[index:index + size] for index in range(0, length, size)] + chunks = [iter(s)] * size + return list(zip(*chunks)) + + +def common_chunks_from_hexdigest(h1, h2, chunk_bytes_length=4): + """ + Compute the number of common chunks of byte length `chunk_bytes_length` between two + strings h1 and h2, each representing a BitAverageHaloHash hexdigest value. + + For example: + + >>> a = '1f22c2c871cd70521211b138cd76fc04' + >>> b = '1f22c2c871cd7852121bbd38c576bc84' + >>> common_chunks_from_hexdigest(a, b, 32) + 1 + + Note: `a` and `b` start with the same 8 characters, where the next groups + of 8 have a few characters off + + >>> byte_hamming_distance(a, b) + 8 + """ + h1 = bitarray_from_bytes(bytes(binascii.unhexlify(h1))) + h2 = bitarray_from_bytes(bytes(binascii.unhexlify(h2))) + h1_slices = slices(h1, chunk_bytes_length) + h2_slices = slices(h2, chunk_bytes_length) + commons = (1 for h1s, h2s in zip(h1_slices, h2_slices) if h1s == h2s) + return sum(commons) + + +def common_chunks(h1, h2, chunk_bytes_length=4): + """ + Compute the number of common chunks of byte length `chunk_bytes_length` between to + hashes h1 and h2 using the digest. + + Note that chunks that are all set to zeroes are matched too: they are be + significant such as empty buckets of bucket hashes. + + For example: + + >>> m1 = b'The value specified for size must be at least as large'.split() + >>> m2 = b'The value specific for size must be at least as large'.split() + >>> a = BitAverageHaloHash(msg=m1, size_in_bits=256) + >>> b = BitAverageHaloHash(msg=m2, size_in_bits=256) + >>> common_chunks(a, b, 2) + 1 + >>> byte_hamming_distance(a.hexdigest(), b.hexdigest()) + 32 + """ + h1_slices = slices(h1.digest(), chunk_bytes_length) + h2_slices = slices(h2.digest(), chunk_bytes_length) + commons = (1 for h1s, h2s in zip(h1_slices, h2_slices) if h1s == h2s) + return sum(commons) + + +def bit_to_num(bits): + """ + Return an int (or long) for a bit array. + + For example: + TODO: test + """ + return int(bits.to01(), 2) + + +# TODO: add test! +def decode_vector(b64_str): + """ + Return a bit array from an encoded string representation. + """ + decoded = codec.urlsafe_b64decode(b64_str) + return bitarray_from_bytes(decoded) diff --git a/matchcode-toolkit/src/matchcode_toolkit/hash.py b/matchcode-toolkit/src/matchcode_toolkit/hash.py new file mode 100644 index 00000000..fbed83b0 --- /dev/null +++ b/matchcode-toolkit/src/matchcode_toolkit/hash.py @@ -0,0 +1,116 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +# From https://github.com/nexB/scancode-toolkit-contrib + +import hashlib + +from commoncode.codec import bin_to_num +from commoncode.codec import urlsafe_b64encode +from commoncode import filetype + +""" +Hashes and checksums. + +Low level hash functions using standard crypto hashes used to construct hashes +of various lengths. Hashes that are smaller than 128 bits are based on a +truncated md5. Other length use SHA hashes. + +Checksums are operating on files. +""" + + +def _hash_mod(bitsize, hmodule): + """ + Return a hashing class returning hashes with a `bitsize` bit length. The + interface of this class is similar to the hash module API. + """ + + class hasher(object): + + def __init__(self, msg=None): + self.digest_size = bitsize // 8 + self.h = msg and hmodule(msg).digest()[:self.digest_size] or None + + def digest(self): + return self.h + + def hexdigest(self): + return self.h and self.h.encode('hex') + + def b64digest(self): + return self.h and urlsafe_b64encode(self.h) + + def intdigest(self): + return self.h and bin_to_num(self.h) + + return hasher + + +# Base hashers for each bit size +_hashmodules_by_bitsize = { + # md5-based + 16: _hash_mod(16, hashlib.md5), + 32: _hash_mod(32, hashlib.md5), + 64: _hash_mod(64, hashlib.md5), + 128: _hash_mod(128, hashlib.md5), + # sha-based + 160: _hash_mod(160, hashlib.sha1), + 256: _hash_mod(256, hashlib.sha256), + 384: _hash_mod(384, hashlib.sha384), + 512: _hash_mod(512, hashlib.sha512) +} + + +def get_hasher(bitsize): + """ + Return a hasher for a given size in bits of the resulting hash. + """ + return _hashmodules_by_bitsize[bitsize] + + +def checksum(location, bitsize, base64=False): + """ + Return a checksum of `bitsize` length from the content of the file at + `location`. The checksum is a hexdigest or base64-encoded is `base64` is + True. + """ + if not filetype.is_file(location): + return + hasher = get_hasher(bitsize) + + # fixme: we should read in chunks + with open(location, 'rb') as f: + hashable = f.read() + + hashed = hasher(hashable) + if base64: + return hashed.b64digest() + + return hashed.hexdigest() + + +def md5(location): + return checksum(location, bitsize=128, base64=False) + + +def sha1(location): + return checksum(location, bitsize=160, base64=False) + + +def b64sha1(location): + return checksum(location, bitsize=160, base64=True) + + +def sha256(location): + return checksum(location, bitsize=256, base64=False) + + +def sha512(location): + return checksum(location, bitsize=512, base64=False) diff --git a/matchcode/plugin_match.py b/matchcode-toolkit/src/matchcode_toolkit/plugin_match.py similarity index 91% rename from matchcode/plugin_match.py rename to matchcode-toolkit/src/matchcode_toolkit/plugin_match.py index 6f87d58b..e6da42e9 100644 --- a/matchcode/plugin_match.py +++ b/matchcode-toolkit/src/matchcode_toolkit/plugin_match.py @@ -15,13 +15,22 @@ from commoncode.cliutils import PluggableCommandLineOption from commoncode.cliutils import POST_SCAN_GROUP -from matchcode.fingerprinting import compute_directory_fingerprints -from matchcode.utils import path_suffixes +from matchcode_toolkit.fingerprinting import compute_directory_fingerprints from plugincode.post_scan import post_scan_impl from plugincode.post_scan import PostScanPlugin -MATCHCODE_DIRECTORY_CONTENT_MATCHING_ENDPOINT = os.environ.get('MATCHCODE_DIRECTORY_CONTENT_MATCHING_ENDPOINT') -MATCHCODE_DIRECTORY_STRUCTURE_MATCHING_ENDPOINT = os.environ.get('MATCHCODE_DIRECTORY_STRUCTURE_MATCHING_ENDPOINT') +MATCHCODE_DIRECTORY_CONTENT_MATCHING_ENDPOINT = "http://127.0.0.1:8001/api/approximate_directory_content_index/match/" #os.environ.get('MATCHCODE_DIRECTORY_CONTENT_MATCHING_ENDPOINT') +MATCHCODE_DIRECTORY_STRUCTURE_MATCHING_ENDPOINT = "http://127.0.0.1:8001/api/approximate_directory_structure_index/match/" #os.environ.get('MATCHCODE_DIRECTORY_STRUCTURE_MATCHING_ENDPOINT') + + +def path_suffixes(path): + """ + Yield all the suffixes of `path`, starting from the longest (e.g. more segments). + """ + segments = path.strip('/').split('/') + suffixes = (segments[i:] for i in range(len(segments))) + for suffix in suffixes: + yield '/'.join(suffix) class PackageInfo: diff --git a/matchcode/api.py b/matchcode/api.py index 3cf13a2e..ca14075d 100644 --- a/matchcode/api.py +++ b/matchcode/api.py @@ -22,12 +22,12 @@ from rest_framework.viewsets import ReadOnlyModelViewSet from matchcode.fingerprinting import create_halohash_chunks +from matchcode.fingerprinting import hexstring_to_binarray from matchcode.fingerprinting import split_fingerprint from matchcode.models import ExactFileIndex from matchcode.models import ExactPackageArchiveIndex from matchcode.models import ApproximateDirectoryContentIndex from matchcode.models import ApproximateDirectoryStructureIndex -from matchcode.utils import hexstring_to_binarray class BaseFileIndexSerializer(ModelSerializer): diff --git a/matchcode/fingerprinting.py b/matchcode/fingerprinting.py index 7192ee7e..472039d2 100644 --- a/matchcode/fingerprinting.py +++ b/matchcode/fingerprinting.py @@ -7,8 +7,9 @@ # See https://aboutcode.org for more information about nexB OSS projects. # +import binascii + from matchcode.halohash import BitAverageHaloHash -from matchcode.utils import hexstring_to_binarray def _create_directory_fingerprint(inputs): @@ -92,6 +93,13 @@ def split_fingerprint(directory_fingerprint): return indexed_elements_count, bah128 +def hexstring_to_binarray(hex_string): + """ + Convert a hex string to binary form, then store in a bytearray + """ + return bytearray(binascii.unhexlify(hex_string)) + + def create_halohash_chunks(bah128): """ Given a 128-bit bah128 hash string, split it into 4 chunks and return those diff --git a/matchcode/models.py b/matchcode/models.py index 842f8dde..27565574 100644 --- a/matchcode/models.py +++ b/matchcode/models.py @@ -19,9 +19,9 @@ from minecode.management.commands import get_error_message from matchcode.fingerprinting import create_halohash_chunks +from matchcode.fingerprinting import hexstring_to_binarray from matchcode.fingerprinting import split_fingerprint from matchcode.halohash import byte_hamming_distance -from matchcode.utils import hexstring_to_binarray from packagedb.models import Package diff --git a/matchcode/tests/test_index_packages.py b/matchcode/tests/test_index_packages.py index c186466f..ee80b47b 100644 --- a/matchcode/tests/test_index_packages.py +++ b/matchcode/tests/test_index_packages.py @@ -12,6 +12,7 @@ from commoncode.resource import VirtualCodebase from matchcode.fingerprinting import compute_directory_fingerprints +from matchcode.fingerprinting import hexstring_to_binarray from matchcode.indexing import _create_virtual_codebase_from_package_resources from matchcode.indexing import index_directory_fingerprints from matchcode.indexing import index_package_archives @@ -21,7 +22,6 @@ from matchcode.models import ApproximateDirectoryContentIndex from matchcode.models import ApproximateDirectoryStructureIndex from matchcode.models import create_halohash_chunks -from matchcode.models import hexstring_to_binarray from matchcode.models import ExactPackageArchiveIndex from matchcode.models import ExactFileIndex from matchcode.utils import load_resources_from_scan diff --git a/matchcode/tests/test_models.py b/matchcode/tests/test_models.py index 38068c03..bee78a03 100644 --- a/matchcode/tests/test_models.py +++ b/matchcode/tests/test_models.py @@ -14,11 +14,11 @@ import attr from matchcode.fingerprinting import compute_directory_fingerprints +from matchcode.fingerprinting import hexstring_to_binarray from matchcode.management.commands.index_packages import index_package_directories from matchcode.models import ApproximateDirectoryContentIndex from matchcode.models import ApproximateDirectoryStructureIndex from matchcode.models import create_halohash_chunks -from matchcode.models import hexstring_to_binarray from matchcode.models import ExactPackageArchiveIndex from matchcode.models import ExactFileIndex from matchcode.utils import index_packages_sha1 diff --git a/matchcode/utils.py b/matchcode/utils.py index 9a04889f..b9e0e9c8 100644 --- a/matchcode/utils.py +++ b/matchcode/utils.py @@ -7,20 +7,19 @@ # See https://aboutcode.org for more information about nexB OSS projects. # -from os import getenv from unittest import TestCase -import binascii + import json import ntpath import os import posixpath -import traceback -from django.conf import settings from django.test import TestCase as DjangoTestCase from commoncode.resource import VirtualCodebase +from matchcode.fingerprinting import hexstring_to_binarray + ############## TEST UTILITIES ############## """ @@ -147,20 +146,3 @@ def index_package_files_sha1(package, scan_location): sha1=sha1_in_bin, package=package, ) - -################# GENERAL UTILITIES ################# -def hexstring_to_binarray(hex_string): - """ - Convert a hex string to binary form, then store in a bytearray - """ - return bytearray(binascii.unhexlify(hex_string)) - - -def path_suffixes(path): - """ - Yield all the suffixes of `path`, starting from the longest (e.g. more segments). - """ - segments = path.strip('/').split('/') - suffixes = (segments[i:] for i in range(len(segments))) - for suffix in suffixes: - yield '/'.join(suffix) diff --git a/minecode/tests/testfiles/directories/find-ls-expected.json b/minecode/tests/testfiles/directories/find-ls-expected.json index 4d4940bb..147a0448 100644 --- a/minecode/tests/testfiles/directories/find-ls-expected.json +++ b/minecode/tests/testfiles/directories/find-ls-expected.json @@ -101,7 +101,7 @@ "path":"groovy/2.4.6/sources/apache-groovy-src-2.4.6.zip", "type":"f", "size":6907454, - "date":"2021-12", + "date":"2022-12", "target":null }, { diff --git a/setup.cfg b/setup.cfg index 2d2fcfbd..c6fc35d2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -74,7 +74,3 @@ docs= Sphinx>=3.3.1 sphinx-rtd-theme>=0.5.0 doc8>=0.8.1 - -[options.entry_points] -scancode_post_scan = - match = matchcode.plugin_match:Match From d4cb0adddc7633304d8ad962c43adafb798f5b44 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Wed, 21 Dec 2022 10:49:20 -0800 Subject: [PATCH 31/38] Make matchcode-toolkit a dependency of matchcode * fingerprinting, halohash will be stored in matchcode-toolkit Signed-off-by: Jono Yang --- Makefile | 3 +- configure | 6 +- .../tests/test_fingerprinting.py | 14 +- .../fingerprinting/abbrev-1.0.3-i.json | 0 matchcode/api.py | 6 +- matchcode/fingerprinting.py | 118 ------ matchcode/halohash.py | 391 ------------------ matchcode/hash.py | 116 ------ matchcode/indexing.py | 2 +- matchcode/models.py | 8 +- matchcode/tests/test_index_packages.py | 4 +- matchcode/tests/test_match.py | 2 +- matchcode/tests/test_models.py | 4 +- matchcode/utils.py | 2 +- setup.cfg | 1 + 15 files changed, 27 insertions(+), 650 deletions(-) rename {matchcode => matchcode-toolkit}/tests/test_fingerprinting.py (89%) rename {matchcode => matchcode-toolkit}/tests/testfiles/fingerprinting/abbrev-1.0.3-i.json (100%) delete mode 100644 matchcode/fingerprinting.py delete mode 100644 matchcode/halohash.py delete mode 100644 matchcode/hash.py diff --git a/Makefile b/Makefile index b9db1e79..c47f4491 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,8 @@ run_map: test: @echo "-> Run the test suite" - ${ACTIVATE} DJANGO_SETTINGS_MODULE=purldb.settings ${PYTHON_EXE} -m pytest -vvs + ${ACTIVATE} DJANGO_SETTINGS_MODULE=purldb.settings ${PYTHON_EXE} -m pytest -vvs --ignore matchcode-toolkit + ${ACTIVATE} ${PYTHON_EXE} -m pytest -vvs matchcode-toolkit shell: ${MANAGE} shell diff --git a/configure b/configure index de5c2083..2e4eff6c 100755 --- a/configure +++ b/configure @@ -30,9 +30,9 @@ CLI_ARGS=$1 CUSTOM_PACKAGES="https://github.com/nexB/commoncode/archive/refs/heads/48-correctly-assign-codebase-attributes.zip https://github.com/nexB/scancode-toolkit/archive/refs/heads/maven-pom-parse-dep-backport.zip" # Requirement arguments passed to pip and used by default or with --dev. -REQUIREMENTS="$CUSTOM_PACKAGES --editable . --constraint requirements.txt" -DEV_REQUIREMENTS="$CUSTOM_PACKAGES --editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt" -DOCS_REQUIREMENTS="$CUSTOM_PACKAGES --editable .[docs] --constraint requirements.txt" +REQUIREMENTS="$CUSTOM_PACKAGES --editable matchcode-toolkit --editable . --constraint requirements.txt" +DEV_REQUIREMENTS="$CUSTOM_PACKAGES --editable matchcode-toolkit --editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt" +DOCS_REQUIREMENTS="$CUSTOM_PACKAGES --editable matchcode-toolkit --editable .[docs] --constraint requirements.txt" # where we create a virtualenv VIRTUALENV_DIR=venv diff --git a/matchcode/tests/test_fingerprinting.py b/matchcode-toolkit/tests/test_fingerprinting.py similarity index 89% rename from matchcode/tests/test_fingerprinting.py rename to matchcode-toolkit/tests/test_fingerprinting.py index 03ed656f..761691f5 100644 --- a/matchcode/tests/test_fingerprinting.py +++ b/matchcode-toolkit/tests/test_fingerprinting.py @@ -12,13 +12,13 @@ from commoncode.resource import VirtualCodebase from commoncode.testcase import FileBasedTesting -from matchcode.fingerprinting import _create_directory_fingerprint -from matchcode.fingerprinting import _get_resource_subpath -from matchcode.fingerprinting import compute_directory_fingerprints -from matchcode.fingerprinting import create_content_fingerprint -from matchcode.fingerprinting import create_halohash_chunks -from matchcode.fingerprinting import create_structure_fingerprint -from matchcode.fingerprinting import split_fingerprint +from matchcode_toolkit.fingerprinting import _create_directory_fingerprint +from matchcode_toolkit.fingerprinting import _get_resource_subpath +from matchcode_toolkit.fingerprinting import compute_directory_fingerprints +from matchcode_toolkit.fingerprinting import create_content_fingerprint +from matchcode_toolkit.fingerprinting import create_halohash_chunks +from matchcode_toolkit.fingerprinting import create_structure_fingerprint +from matchcode_toolkit.fingerprinting import split_fingerprint class Resource(): diff --git a/matchcode/tests/testfiles/fingerprinting/abbrev-1.0.3-i.json b/matchcode-toolkit/tests/testfiles/fingerprinting/abbrev-1.0.3-i.json similarity index 100% rename from matchcode/tests/testfiles/fingerprinting/abbrev-1.0.3-i.json rename to matchcode-toolkit/tests/testfiles/fingerprinting/abbrev-1.0.3-i.json diff --git a/matchcode/api.py b/matchcode/api.py index ca14075d..f20dd166 100644 --- a/matchcode/api.py +++ b/matchcode/api.py @@ -21,9 +21,9 @@ from rest_framework.serializers import Serializer from rest_framework.viewsets import ReadOnlyModelViewSet -from matchcode.fingerprinting import create_halohash_chunks -from matchcode.fingerprinting import hexstring_to_binarray -from matchcode.fingerprinting import split_fingerprint +from matchcode_toolkit.fingerprinting import create_halohash_chunks +from matchcode_toolkit.fingerprinting import hexstring_to_binarray +from matchcode_toolkit.fingerprinting import split_fingerprint from matchcode.models import ExactFileIndex from matchcode.models import ExactPackageArchiveIndex from matchcode.models import ApproximateDirectoryContentIndex diff --git a/matchcode/fingerprinting.py b/matchcode/fingerprinting.py deleted file mode 100644 index 472039d2..00000000 --- a/matchcode/fingerprinting.py +++ /dev/null @@ -1,118 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -import binascii - -from matchcode.halohash import BitAverageHaloHash - - -def _create_directory_fingerprint(inputs): - """ - Return a 128-bit BitAverageHaloHash fingerprint in hex from `inputs` - """ - inputs = [i.encode('utf-8') for i in inputs if i] - bah128 = BitAverageHaloHash(inputs, size_in_bits=128).hexdigest() - inputs_count = len(inputs) - inputs_count_hex_str = '%08x' % inputs_count - bah128 = bah128.decode('utf-8') - directory_fingerprint = inputs_count_hex_str + bah128 - return directory_fingerprint - - -def create_content_fingerprint(resources): - """ - Collect SHA1 strings from a list of Resources (`resources`) and create a - directory fingerprint from them - """ - features = [r.sha1 for r in resources if r.sha1] - return _create_directory_fingerprint(features) - - -def _get_resource_subpath(resource, top): - """ - Return the subpath of `resource` relative to `top` from `codebase` - - For example: - - top.path = 'foo/bar/' - resource.path = 'foo/bar/baz.c' - - The subpath returned would be 'baz.c' - """ - _, _, subpath = resource.path.partition(top.path) - subpath = subpath.lstrip('/') - return subpath - - -def create_structure_fingerprint(directory, children): - """ - Collect the subpaths of children Resources of Resource `directory` and - create a fingerprint from them - """ - features = [] - for child in children: - if not child.path: - continue - child_subpath = _get_resource_subpath(child, directory) - rounded_child_size = int(child.size / 10) * 10 - path_feature = str(rounded_child_size) + child_subpath - features.append(path_feature) - return _create_directory_fingerprint(features) - - -def compute_directory_fingerprints(codebase): - """ - Compute fingerprints for a directory from `codebase` - """ - for resource in codebase.walk(topdown=False): - if resource.is_file or not resource.path: - continue - children = [r for r in resource.walk(codebase) if r.is_file] - if len(children) == 1: - continue - resource.extra_data['directory_content'] = create_content_fingerprint(children) - resource.extra_data['directory_structure'] = create_structure_fingerprint(resource, children) - resource.save(codebase) - return codebase - - -def split_fingerprint(directory_fingerprint): - """ - Given a string `directory_fingerprint`, return the indexed elements count as - an integer and the bah128 fingerprint string - """ - indexed_elements_count_hash = directory_fingerprint[0:8] - indexed_elements_count = int(indexed_elements_count_hash, 16) - bah128 = directory_fingerprint[8:] - return indexed_elements_count, bah128 - - -def hexstring_to_binarray(hex_string): - """ - Convert a hex string to binary form, then store in a bytearray - """ - return bytearray(binascii.unhexlify(hex_string)) - - -def create_halohash_chunks(bah128): - """ - Given a 128-bit bah128 hash string, split it into 4 chunks and return those - chunks as bytearrays - """ - chunk1 = bah128[0:8] - chunk2 = bah128[8:16] - chunk3 = bah128[16:24] - chunk4 = bah128[24:32] - - chunk1 = hexstring_to_binarray(chunk1) - chunk2 = hexstring_to_binarray(chunk2) - chunk3 = hexstring_to_binarray(chunk3) - chunk4 = hexstring_to_binarray(chunk4) - - return chunk1, chunk2, chunk3, chunk4 diff --git a/matchcode/halohash.py b/matchcode/halohash.py deleted file mode 100644 index 371be7cf..00000000 --- a/matchcode/halohash.py +++ /dev/null @@ -1,391 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -import binascii - -from bitarray import bitarray -from bitarray.util import count_xor - -from commoncode import codec - -from matchcode import hash as commoncode_hash - -""" -Halo is a family of hash functions that have the un-common property that mostly -similar -- but not identical -- inputs will hash to very similar outputs. This -type of hash function is sometimes called a locality-sensitive hash function, -because it is sensitive to the locality of the data being hashed. - -The purpose of these hashes is to quickly compare a large number of elements -that are likely to be similar to find candidates and then compute a more -comprehensive similarity only on the candidates. This includes goals such as -identifying near-duplicates of things or to group very similar things together -(a.k.a. clustering), as well as to detect similarities between inputs or perform -quick comparisons under a certain threshold. - -For a traditional 'good' hash function, small changes in the input will yield -very different hash outputs (through diffusion and avalanche effect). For -instance, cryptographic hashes such as SHA1 or MD5 behave this way. If you hash -two bit strings with a SHA1 function and there is only one bit of difference -between these two strings then the resulting hashes will be rather different. On -average, each time one bit is added to the input, good hash functions have half -of the output bits switched from 0 to 1. - -A Halo hash instead hashes similar inputs to the same hash or to a hash that -differs only by a few bits. The similarity between two hashes becomes an -approximation of the similarity between the two original inputs. This simalirity -is computed using the hamming distance or number of non-matching bits between -two hashes outputs bit straings. This hamming distance is roughly proportional -to the similarity between the two original inputs and can be used to estimate -the similarity of inputs without having access to these full input. - -The Halo name is a play on what one of the hashing function does: a halo is like -a fuzzy, halo'ish representation of the input. - -The bit average function ressembles Charikar's algorithm by using each bits in an -array of hashes but does not use a TF/IDF resulting in a simpler procedure. -""" - - -class BitAverageHaloHash(object): - """ - A bit matrix averaging hash. - - The high level processing sketch looks like this: - For an input of: - ['this' ,'is', 'a', 'rose', 'great']: - - * we first hash each list item to get something like - [4, 15, 2, 12, 12] (for instance with a very short hash function of 4 bits output) - - or as bits this would be something like this: - - ['0011', - '1110', - '0010', - '1100', - '1100'] - - * we sum up each bit positions/columns together: - ['0011', - '1110', - '0010', - '1100', - '1100'] - ------- - 3331 - - or stated otherwise: pos1=3, pos2=3, pos3=3, pos4=1 - - * The mean value for a column is number of hashes/2 (2 because we use bits). - Here mean = 5 hashes/2 = 2.5 - - * We compare the sum of each position with the mean and yield a bit: - if pos sum > mean yield 1 else yield 0 - position1 = 3 > mean = 2.5 , then bit=1 - position2 = 3 > mean = 2.5 , then bit=1 - position3 = 3 > mean = 2.5 , then bit=1 - position4 = 1 < mean = 2.5 , then bit=0 - - * We build a hash by concatenating the resulting bits: - pos 1 + pos2 + pos3 + pos4 = '1110' - - In general, this hash seems to show a lower accuracy and higher sensitivity - with small string and small inputs variations than the bucket average hash. - But it works better on shorter inputs. - - Some usage examples: - - >>> z = b'''The value specified for size must be at - ... least as large as for the smallest bit vector possible for intVal'''.split() - >>> a = BitAverageHaloHash(z, size_in_bits=256) - >>> len(a.digest()) - 32 - >>> z = b'''The value specified for size must be no - ... more larger than the smallest bit vector possible for intVal'''.split() - >>> b = BitAverageHaloHash(z, size_in_bits=256) - >>> a.distance(b) - 57 - >>> b.distance(a) - 57 - >>> a = BitAverageHaloHash(size_in_bits=160) - >>> z = [a.update(x) for x in b'''The value specified for size must be at - ... least as large as for the smallest bit vector possible for intVal'''.split()] - >>> assert a.hexdigest() == b'2c10223104c43470e10b1157e6415b2f730057d0' - >>> b = BitAverageHaloHash(size_in_bits=160) - >>> z = [b.update(x) for x in b'''The value specified for size must be no - ... more larger than the smallest bit vector possible for intVal'''.split()] - >>> assert b.hexdigest() == b'2c912433c4c624e0b03b34576641df8fe00017d0' - >>> a.distance(b) - 29 - >>> a = BitAverageHaloHash(size_in_bits=128) - >>> z =[a.update(x) for x in b'''The value specified for size must be at - ... least as large as for the smallest bit vector possible for intVal'''.split()] - >>> assert a.hexdigest() == b'028b1699c0c5310cd1b566a893d12f10' - >>> b = BitAverageHaloHash(size_in_bits=128) - >>> z = [b.update(x) for x in b'''The value specified for size must be no - ... more larger than the smallest bit vector possible for intVal'''.split()] - >>> assert b.hexdigest() == b'0002969060d5b344d1b7602cd9e127b0' - >>> a.distance(b) - 27 - >>> a = BitAverageHaloHash(size_in_bits=64) - >>> z = [a.update(x) for x in b'''The value specified for size must be at - ... least as large as for the smallest bit vector possible for intVal'''.split()] - >>> assert a.hexdigest() == b'028b1699c0c5310c' - >>> b = BitAverageHaloHash(size_in_bits=64) - >>> z = [b.update(x) for x in b'''The value specified for size must be no - ... more larger than the smallest bit vector possible for intVal'''.split()] - >>> assert b.hexdigest() == b'0002969060d5b344' - >>> a.distance(b) - 14 - >>> a = BitAverageHaloHash(size_in_bits=32) - >>> z = [a.update(x) for x in b'''The value specified for size must be at - ... least as large as for the smallest bit vector possible for intVal'''.split()] - >>> b = BitAverageHaloHash(size_in_bits=32) - >>> z = [b.update(x) for x in b'''The value specified for size must be at - ... least as large as for the smallest bit vector possible by intVal'''.split()] - >>> a.distance(b) - 5 - >>> a = BitAverageHaloHash(size_in_bits=512) - >>> z = [a.update(x) for x in b'''The value specified for size must be at - ... least as large as for the smallest bit vector possible for intVal'''.split()] - >>> b = BitAverageHaloHash(size_in_bits=512) - >>> z = [b.update(x) for x in b'''The value specified for size must be at - ... least as large as for the smallest bit vector possible by intVal'''.split()] - >>> a.distance(b) - 46 - """ - - # TODO: Keep state, keep 1 position per column - - # TODO: create method to aggregate multiple BitAverageHaloHashes together - # TODO: refactor this, don't keep all hashes - # TODO: keep only a list of columns - def __init__(self, msg=None, size_in_bits=128): - self.size_in_bits = size_in_bits - self.columns = [0] * size_in_bits - - # TODO: pick one hash module instead of selecting from multiple hash modules - self.hashmodule = lambda x: x - try: - # TODO: pick one hash algorithm - self.hashmodule = commoncode_hash.get_hasher(size_in_bits) - except: - raise Exception('No available hash module for the requested ' - 'hash size in bits: %(size_in_bits)d' % locals()) - self.update(msg) - - @property - def digest_size(self): - return self.size_in_bits // 8 - - def update(self, msg): - """ - Append a bytestring or sequence of bytestrings to the hash. - """ - if not msg: - return - if isinstance(msg, (list, tuple,)): - for m in msg: - self.__hashup(m) - else: - self.__hashup(msg) - - def __hashup(self, msg): - assert isinstance(msg, bytes) - hsh = self.hashmodule(msg).digest() - bits = bitarray_from_bytes(hsh) - normalized = (-1 if v else 1 for v in bits) - for i, column in enumerate(normalized): - self.columns[i] += column - - def hexdigest(self): - """ - Return the hex-encoded hash value. - """ - return binascii.hexlify(self.digest()) - - def b64digest(self): - """ - Return a base64 "url safe"-encoded string representing this hash. - """ - return codec.b64encode(self.digest()) - - def digest(self): - """ - Return a binary string representing this hash. - """ - flattened = [1 if col > 0 else 0 for col in self.columns] - bits = bitarray(flattened) - return bits.tobytes() - - def distance(self, other): - """ - Return the bit Hamming distance between this hash and another hash. - """ - return int(count_xor(self.hash(), other.hash())) - - def hash(self): - return bitarray_from_bytes(self.digest()) - - @classmethod - def combine(cls, hashes): - """ - Return a BitAverageHaloHash by summing and averaging the columns of the - BitAverageHaloHashes in `hashes` together, putting the resulting - columns into a new BitAverageHaloHash and returning it - """ - size_in_bits = hashes[0].size_in_bits - for h in hashes: - assert isinstance(hash, cls), 'all hashes should be a BitAverageHaloHash, not {}'.format(type(h)) - assert h.size_in_bits == size_in_bits - - all_columns = [h.columns for h in hashes] - b = cls() - b.columns = [sum(col) for col in zip(*all_columns)] - return b - - -def bitarray_from_bytes(b): - """ - Return a bitarray built from a byte string b. - """ - a = bitarray() - a.frombytes(b) - return a - - -def byte_hamming_distance(b1, b2): - b1 = binascii.unhexlify(b1) - b2 = binascii.unhexlify(b2) - b1 = bitarray_from_bytes(b1) - b2 = bitarray_from_bytes(b2) - return hamming_distance(b1, b2) - - -def hamming_distance(bv1, bv2): - """ - Return the Hamming distance between `bv1` and `bv2` bitvectors as the - number of equal bits for all positions. (e.g. the count of bits set to one - in an XOR between two bit strings.) - - `bv1` and `bv2` must both be either hash-like Halohash instances (with a - hash() function) or bit array instances (that can be manipulated as-is). - - See http://en.wikipedia.org/wiki/Hamming_distance - - For example: - - >>> b1 = bitarray('0001010111100001111') - >>> b2 = bitarray('0001010111100001111') - >>> hamming_distance(b1, b2) - 0 - >>> b1 = bitarray('11110000') - >>> b2 = bitarray('00001111') - >>> hamming_distance(b1, b2) - 8 - >>> b1 = bitarray('11110000') - >>> b2 = bitarray('00110011') - >>> hamming_distance(b1, b2) - 4 - """ - return int(count_xor(bv1, bv2)) - - -def slices(s, size): - """ - Given a sequence s, return a sequence of non-overlapping slices of `size`. - Raise an AssertionError if the sequence length is not a multiple of `size`. - - For example: - >>> slices([1, 2, 3, 4, 5, 6], 2) - [(1, 2), (3, 4), (5, 6)] - >>> slices([1, 2, 3, 4, 5, 6], 3) - [(1, 2, 3), (4, 5, 6)] - >>> try: - ... slices([1, 2, 3, 4, 5, 6], 4) - ... except AssertionError: - ... pass - """ - length = len(s) - assert length % size == 0, 'Invalid slice size: len(%(s)r) is not a multiple of %(size)r' % locals() - # TODO: time alternative - # return [s[index:index + size] for index in range(0, length, size)] - chunks = [iter(s)] * size - return list(zip(*chunks)) - - -def common_chunks_from_hexdigest(h1, h2, chunk_bytes_length=4): - """ - Compute the number of common chunks of byte length `chunk_bytes_length` between two - strings h1 and h2, each representing a BitAverageHaloHash hexdigest value. - - For example: - - >>> a = '1f22c2c871cd70521211b138cd76fc04' - >>> b = '1f22c2c871cd7852121bbd38c576bc84' - >>> common_chunks_from_hexdigest(a, b, 32) - 1 - - Note: `a` and `b` start with the same 8 characters, where the next groups - of 8 have a few characters off - - >>> byte_hamming_distance(a, b) - 8 - """ - h1 = bitarray_from_bytes(bytes(binascii.unhexlify(h1))) - h2 = bitarray_from_bytes(bytes(binascii.unhexlify(h2))) - h1_slices = slices(h1, chunk_bytes_length) - h2_slices = slices(h2, chunk_bytes_length) - commons = (1 for h1s, h2s in zip(h1_slices, h2_slices) if h1s == h2s) - return sum(commons) - - -def common_chunks(h1, h2, chunk_bytes_length=4): - """ - Compute the number of common chunks of byte length `chunk_bytes_length` between to - hashes h1 and h2 using the digest. - - Note that chunks that are all set to zeroes are matched too: they are be - significant such as empty buckets of bucket hashes. - - For example: - - >>> m1 = b'The value specified for size must be at least as large'.split() - >>> m2 = b'The value specific for size must be at least as large'.split() - >>> a = BitAverageHaloHash(msg=m1, size_in_bits=256) - >>> b = BitAverageHaloHash(msg=m2, size_in_bits=256) - >>> common_chunks(a, b, 2) - 1 - >>> byte_hamming_distance(a.hexdigest(), b.hexdigest()) - 32 - """ - h1_slices = slices(h1.digest(), chunk_bytes_length) - h2_slices = slices(h2.digest(), chunk_bytes_length) - commons = (1 for h1s, h2s in zip(h1_slices, h2_slices) if h1s == h2s) - return sum(commons) - - -def bit_to_num(bits): - """ - Return an int (or long) for a bit array. - - For example: - TODO: test - """ - return int(bits.to01(), 2) - - -# TODO: add test! -def decode_vector(b64_str): - """ - Return a bit array from an encoded string representation. - """ - decoded = codec.urlsafe_b64decode(b64_str) - return bitarray_from_bytes(decoded) diff --git a/matchcode/hash.py b/matchcode/hash.py deleted file mode 100644 index fbed83b0..00000000 --- a/matchcode/hash.py +++ /dev/null @@ -1,116 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# purldb is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/purldb for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -# From https://github.com/nexB/scancode-toolkit-contrib - -import hashlib - -from commoncode.codec import bin_to_num -from commoncode.codec import urlsafe_b64encode -from commoncode import filetype - -""" -Hashes and checksums. - -Low level hash functions using standard crypto hashes used to construct hashes -of various lengths. Hashes that are smaller than 128 bits are based on a -truncated md5. Other length use SHA hashes. - -Checksums are operating on files. -""" - - -def _hash_mod(bitsize, hmodule): - """ - Return a hashing class returning hashes with a `bitsize` bit length. The - interface of this class is similar to the hash module API. - """ - - class hasher(object): - - def __init__(self, msg=None): - self.digest_size = bitsize // 8 - self.h = msg and hmodule(msg).digest()[:self.digest_size] or None - - def digest(self): - return self.h - - def hexdigest(self): - return self.h and self.h.encode('hex') - - def b64digest(self): - return self.h and urlsafe_b64encode(self.h) - - def intdigest(self): - return self.h and bin_to_num(self.h) - - return hasher - - -# Base hashers for each bit size -_hashmodules_by_bitsize = { - # md5-based - 16: _hash_mod(16, hashlib.md5), - 32: _hash_mod(32, hashlib.md5), - 64: _hash_mod(64, hashlib.md5), - 128: _hash_mod(128, hashlib.md5), - # sha-based - 160: _hash_mod(160, hashlib.sha1), - 256: _hash_mod(256, hashlib.sha256), - 384: _hash_mod(384, hashlib.sha384), - 512: _hash_mod(512, hashlib.sha512) -} - - -def get_hasher(bitsize): - """ - Return a hasher for a given size in bits of the resulting hash. - """ - return _hashmodules_by_bitsize[bitsize] - - -def checksum(location, bitsize, base64=False): - """ - Return a checksum of `bitsize` length from the content of the file at - `location`. The checksum is a hexdigest or base64-encoded is `base64` is - True. - """ - if not filetype.is_file(location): - return - hasher = get_hasher(bitsize) - - # fixme: we should read in chunks - with open(location, 'rb') as f: - hashable = f.read() - - hashed = hasher(hashable) - if base64: - return hashed.b64digest() - - return hashed.hexdigest() - - -def md5(location): - return checksum(location, bitsize=128, base64=False) - - -def sha1(location): - return checksum(location, bitsize=160, base64=False) - - -def b64sha1(location): - return checksum(location, bitsize=160, base64=True) - - -def sha256(location): - return checksum(location, bitsize=256, base64=False) - - -def sha512(location): - return checksum(location, bitsize=512, base64=False) diff --git a/matchcode/indexing.py b/matchcode/indexing.py index 27604d02..e4289031 100644 --- a/matchcode/indexing.py +++ b/matchcode/indexing.py @@ -13,7 +13,7 @@ from commoncode.resource import VirtualCodebase -from matchcode.fingerprinting import compute_directory_fingerprints +from matchcode_toolkit.fingerprinting import compute_directory_fingerprints from matchcode.models import ApproximateDirectoryContentIndex from matchcode.models import ApproximateDirectoryStructureIndex from matchcode.models import ExactPackageArchiveIndex diff --git a/matchcode/models.py b/matchcode/models.py index 27565574..5321d16e 100644 --- a/matchcode/models.py +++ b/matchcode/models.py @@ -18,10 +18,10 @@ from django.utils.translation import gettext_lazy as _ from minecode.management.commands import get_error_message -from matchcode.fingerprinting import create_halohash_chunks -from matchcode.fingerprinting import hexstring_to_binarray -from matchcode.fingerprinting import split_fingerprint -from matchcode.halohash import byte_hamming_distance +from matchcode_toolkit.fingerprinting import create_halohash_chunks +from matchcode_toolkit.fingerprinting import hexstring_to_binarray +from matchcode_toolkit.fingerprinting import split_fingerprint +from matchcode_toolkit.halohash import byte_hamming_distance from packagedb.models import Package diff --git a/matchcode/tests/test_index_packages.py b/matchcode/tests/test_index_packages.py index ee80b47b..f6850d33 100644 --- a/matchcode/tests/test_index_packages.py +++ b/matchcode/tests/test_index_packages.py @@ -11,8 +11,8 @@ from commoncode.resource import VirtualCodebase -from matchcode.fingerprinting import compute_directory_fingerprints -from matchcode.fingerprinting import hexstring_to_binarray +from matchcode_toolkit.fingerprinting import compute_directory_fingerprints +from matchcode_toolkit.fingerprinting import hexstring_to_binarray from matchcode.indexing import _create_virtual_codebase_from_package_resources from matchcode.indexing import index_directory_fingerprints from matchcode.indexing import index_package_archives diff --git a/matchcode/tests/test_match.py b/matchcode/tests/test_match.py index c5057740..0488edcb 100644 --- a/matchcode/tests/test_match.py +++ b/matchcode/tests/test_match.py @@ -13,7 +13,7 @@ from commoncode.resource import VirtualCodebase from packagedb.models import Package -from matchcode.fingerprinting import compute_directory_fingerprints +from matchcode_toolkit.fingerprinting import compute_directory_fingerprints from matchcode.management.commands.index_packages import index_package_directories from matchcode.match import EXACT_PACKAGE_ARCHIVE_MATCH from matchcode.match import APPROXIMATE_DIRECTORY_STRUCTURE_MATCH diff --git a/matchcode/tests/test_models.py b/matchcode/tests/test_models.py index bee78a03..ae6b9e06 100644 --- a/matchcode/tests/test_models.py +++ b/matchcode/tests/test_models.py @@ -13,8 +13,8 @@ from packagedb.models import Package import attr -from matchcode.fingerprinting import compute_directory_fingerprints -from matchcode.fingerprinting import hexstring_to_binarray +from matchcode_toolkit.fingerprinting import compute_directory_fingerprints +from matchcode_toolkit.fingerprinting import hexstring_to_binarray from matchcode.management.commands.index_packages import index_package_directories from matchcode.models import ApproximateDirectoryContentIndex from matchcode.models import ApproximateDirectoryStructureIndex diff --git a/matchcode/utils.py b/matchcode/utils.py index b9e0e9c8..e2ddd278 100644 --- a/matchcode/utils.py +++ b/matchcode/utils.py @@ -18,7 +18,7 @@ from commoncode.resource import VirtualCodebase -from matchcode.fingerprinting import hexstring_to_binarray +from matchcode_toolkit.fingerprinting import hexstring_to_binarray ############## TEST UTILITIES ############## diff --git a/setup.cfg b/setup.cfg index c6fc35d2..73fcf9fb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,6 +54,7 @@ install_requires = rubymarshal == 1.0.3 scancode-toolkit == 31.2.2 urlpy == 0.5 + matchcode-toolkit setup_requires = setuptools_scm[toml] >= 4 python_requires = >=3.8.* From a0d7f09464512fe8fcc9041476b83e000146bc4d Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Wed, 21 Dec 2022 11:36:23 -0800 Subject: [PATCH 32/38] Update matchcode-toolkit README.rst Signed-off-by: Jono Yang --- matchcode-toolkit/AUTHORS.rst | 2 +- matchcode-toolkit/README.rst | 64 +++-------------------------------- 2 files changed, 6 insertions(+), 60 deletions(-) diff --git a/matchcode-toolkit/AUTHORS.rst b/matchcode-toolkit/AUTHORS.rst index 51a19cc8..0fd6530e 100644 --- a/matchcode-toolkit/AUTHORS.rst +++ b/matchcode-toolkit/AUTHORS.rst @@ -1,3 +1,3 @@ The following organizations or individuals have contributed to this repo: -- +- Jono Yang diff --git a/matchcode-toolkit/README.rst b/matchcode-toolkit/README.rst index be657342..a0b99054 100644 --- a/matchcode-toolkit/README.rst +++ b/matchcode-toolkit/README.rst @@ -1,59 +1,5 @@ -A Simple Python Project Skeleton -================================ -This repo attempts to standardize the structure of the Python-based project's -repositories using modern Python packaging and configuration techniques. -Using this `blog post`_ as inspiration, this repository serves as the base for -all new Python projects and is mergeable in existing repositories as well. - -.. _blog post: https://blog.jaraco.com/a-project-skeleton-for-python-projects/ - - -Usage -===== - -A brand new project -------------------- -.. code-block:: bash - - git init my-new-repo - cd my-new-repo - git pull git@github.com:nexB/skeleton - - # Create the new repo on GitHub, then update your remote - git remote set-url origin git@github.com:nexB/your-new-repo.git - -From here, you can make the appropriate changes to the files for your specific project. - -Update an existing project ---------------------------- -.. code-block:: bash - - cd my-existing-project - git remote add skeleton git@github.com:nexB/skeleton - git fetch skeleton - git merge skeleton/main --allow-unrelated-histories - -This is also the workflow to use when updating the skeleton files in any given repository. - -More usage instructions can be found in ``docs/skeleton-usage.rst``. - - -Release Notes -============= - -- 2022-03-04: - - Synchronize configure and configure.bat scripts for sanity - - Update CI operating system support with latest Azure OS images - - Streamline utility scripts in etc/scripts/ to create, fetch and manage third-party dependencies - There are now fewer scripts. See etc/scripts/README.rst for details - -- 2021-09-03: - - ``configure`` now requires pinned dependencies via the use of ``requirements.txt`` and ``requirements-dev.txt`` - - ``configure`` can now accept multiple options at once - - Add utility scripts from scancode-toolkit/etc/release/ for use in generating project files - - Rename virtual environment directory from ``tmp`` to ``venv`` - - Update README.rst with instructions for generating ``requirements.txt`` and ``requirements-dev.txt``, - as well as collecting dependencies as wheels and generating ABOUT files for them. - -- 2021-05-11: - - Adopt new configure scripts from ScanCode TK that allows correct configuration of which Python version is used. +matchcode-toolkit +================= +This contains a scancode-toolkit post-scan plugin that fingerprints the +directories of a scan and queries those fingerprints against the matchcode API +to find package matches. From dbcab0eb22b11c456a9dbbc7cdc519185e8813bf Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Wed, 21 Dec 2022 12:24:14 -0800 Subject: [PATCH 33/38] State project name and version in pyproject.toml Signed-off-by: Jono Yang --- matchcode-toolkit/pyproject.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/matchcode-toolkit/pyproject.toml b/matchcode-toolkit/pyproject.toml index 11534517..87592532 100644 --- a/matchcode-toolkit/pyproject.toml +++ b/matchcode-toolkit/pyproject.toml @@ -1,3 +1,7 @@ +[project] +name = "matchcode-toolkit" +version = "0.0.1" + [build-system] requires = ["setuptools >= 50", "wheel", "setuptools_scm[toml] >= 6"] build-backend = "setuptools.build_meta" @@ -5,7 +9,7 @@ build-backend = "setuptools.build_meta" [tool.setuptools_scm] # this is used populated when creating a git archive # and when there is .git dir and/or there is no git installed -fallback_version = "0.0.1" +fallback_version = "9999.$Format:%h-%cs$" [tool.pytest.ini_options] norecursedirs = [ From ef4b9be2b1eca25a038e6bb3a4b6d1c8852555f7 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Wed, 21 Dec 2022 16:55:24 -0800 Subject: [PATCH 34/38] Update matchcode-toolkit README.rst * Add some usage instructions Signed-off-by: Jono Yang --- matchcode-toolkit/README.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/matchcode-toolkit/README.rst b/matchcode-toolkit/README.rst index a0b99054..b2a68239 100644 --- a/matchcode-toolkit/README.rst +++ b/matchcode-toolkit/README.rst @@ -3,3 +3,23 @@ matchcode-toolkit This contains a scancode-toolkit post-scan plugin that fingerprints the directories of a scan and queries those fingerprints against the matchcode API to find package matches. + + +Usage +----- + +Ensure that the PurlDB server is up. Set the following environment variables: + * ``MATCHCODE_DIRECTORY_CONTENT_MATCHING_ENDPOINT`` + * ``export MATCHCODE_DIRECTORY_CONTENT_MATCHING_ENDPOINT="http://127.0.0.1:8001/api/approximate_directory_content_index/match/"`` + * ``MATCHCODE_DIRECTORY_STRUCTURE_MATCHING_ENDPOINT`` + * ``export MATCHCODE_DIRECTORY_STRUCTURE_MATCHING_ENDPOINT="http://127.0.0.1:8001/api/approximate_directory_structure_index/match/"`` + +Install the matchcode-toolkit plugin into scancode-toolkit: + * Open a shell and enable the virtual environment of the scancode-toolkit instance you want to use + * Navigate to the matchcode-toolkit directory and run ``pip install -e .`` + +Run scancode with matching enabled: + * The ``--info`` option has to be enabled on the scan you are running: + * ``scancode --info --match --json-pp -`` + or on the scan you are importing: + * ``scancode --from-scan --match --json-pp -`` From 800218e349b8b7fff14606efc40d58c3b185df21 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Tue, 3 Jan 2023 11:35:44 -0800 Subject: [PATCH 35/38] Bump spdx-tools version Signed-off-by: Jono Yang --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 16cc9a69..8171b4a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -83,7 +83,7 @@ scancode-toolkit @ https://github.com/nexB/scancode-toolkit/archive/refs/heads/m setuptools==57.0.0 six==1.16.0 soupsieve==2.3.2.post1 -spdx-tools==0.7.0a3 +spdx-tools==0.7.0rc0 sqlparse==0.4.3 text-unidecode==1.3 toml==0.10.2 From c75f8e4e3a4fc49035a3c1283d6dadbb563b3a65 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Tue, 3 Jan 2023 11:38:52 -0800 Subject: [PATCH 36/38] Fix nested list in matchcode-toolkit/README.rst Signed-off-by: Jono Yang --- matchcode-toolkit/README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/matchcode-toolkit/README.rst b/matchcode-toolkit/README.rst index b2a68239..aa5f165c 100644 --- a/matchcode-toolkit/README.rst +++ b/matchcode-toolkit/README.rst @@ -10,8 +10,11 @@ Usage Ensure that the PurlDB server is up. Set the following environment variables: * ``MATCHCODE_DIRECTORY_CONTENT_MATCHING_ENDPOINT`` + * ``export MATCHCODE_DIRECTORY_CONTENT_MATCHING_ENDPOINT="http://127.0.0.1:8001/api/approximate_directory_content_index/match/"`` + * ``MATCHCODE_DIRECTORY_STRUCTURE_MATCHING_ENDPOINT`` + * ``export MATCHCODE_DIRECTORY_STRUCTURE_MATCHING_ENDPOINT="http://127.0.0.1:8001/api/approximate_directory_structure_index/match/"`` Install the matchcode-toolkit plugin into scancode-toolkit: @@ -20,6 +23,9 @@ Install the matchcode-toolkit plugin into scancode-toolkit: Run scancode with matching enabled: * The ``--info`` option has to be enabled on the scan you are running: + * ``scancode --info --match --json-pp -`` + or on the scan you are importing: + * ``scancode --from-scan --match --json-pp -`` From d4e3c3518f87475a513efd13360daded1c2d332a Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Tue, 3 Jan 2023 11:40:31 -0800 Subject: [PATCH 37/38] Update AUTHORS.rst Signed-off-by: Jono Yang --- AUTHORS.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index c3f8bfc8..c6d13b91 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -4,4 +4,5 @@ The following organizations or individuals have contributed to this repo: - Jono Yang - Philippe Ombredanne - Li Ha -- Steven Esser \ No newline at end of file +- Steven Esser +- Armin Tänzer From 9504d0cb0a205bee15f4333027fe4ae840920236 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Tue, 3 Jan 2023 11:49:11 -0800 Subject: [PATCH 38/38] Update CHANGELOG.rst Signed-off-by: Jono Yang --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6c3e42ee..eda3bcfb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,10 @@ Changelog ========= +next-version +------------ + +*2023-01-03* -- Add clearcode, matchcode, and matchcode-toolkit to purldb. Reorganize code such that purldb is a single Django app. v2.0.0 ------