diff --git a/fetchcode/purl2url/__init__.py b/fetchcode/purl2url/__init__.py new file mode 100644 index 00000000..5b3f2168 --- /dev/null +++ b/fetchcode/purl2url/__init__.py @@ -0,0 +1,173 @@ +# fetchcode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/fetchcode for support and download. +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# http://nexb.com and http://aboutcode.org +# +# This software is licensed under the Apache License version 2.0. +# +# 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. + +from urllib.parse import urlparse + + +def index_in_list(index, list): + return index < len(list) + + +def build_bitbucket(**data): + """ + Take dictionary `data` as input and returns a valid bitbucket URL string `url` + """ + + name = data["name"] + namespace = data["namespace"] + if not (name and namespace): + return + + url = "https://bitbucket.org/{namespace}/{name}".format( + namespace=namespace, name=name + ) + version = data["version"] + if version: + url = "{url}/src/{version}".format(url=url, version=version) + + subpath = data["subpath"] + if subpath: + url = "{url}/{subpath}".format(url=url, subpath=subpath) + + return url + + +def build_cargo(**data): + """ + Take dictionary `data` as input and returns a valid cargo URL string `url` + """ + name = data["name"] + version = data["version"] + if not (name and version): + return + + return "https://crates.io/api/v1/crates/{name}/{version}/download".format( + name=name, version=version + ) + + +def build_github(**data): + """ + Take dictionary `data` as input and returns a valid github URL string `url` + """ + name = data["name"] + namespace = data["namespace"] + if not (name and namespace): + return + + url = "https://github.com/{namespace}/{name}".format(namespace=namespace, name=name) + + version = data["version"] + if version: + url = "{url}/tree/{version}".format(url=url, version=version) + + subpath = data["subpath"] + if subpath: + url = "{url}/{subpath}".format(url=url, subpath=subpath) + + return url + + +def build_gitlab(**data): + """ + Take dictionary `data` as input and returns a valid gitlab URL string `url` + """ + name = data["name"] + namespace = data["namespace"] + + if not (name and namespace): + return + + url = "https://gitlab.com/{namespace}/{name}".format(namespace=namespace, name=name) + + version = data["version"] + if version: + url = "{url}/-/tree/{version}".format(url=url, version=version) + + subpath = data["subpath"] + if subpath: + url = "{url}/{subpath}".format(url=url, subpath=subpath) + + return url + + +def build_gem(**data): + """ + Take dictionary `data` as input and returns a valid rubygem URL string `url` + """ + name = data["name"] + version = data["version"] + + if not (name and version): + return + + return "https://rubygems.org/downloads/{name}-{version}.gem".format( + name=name, version=version + ) + + +def purl2url(purl): + """ + Take PackageURL `purl` as input and return a valid URL string `url` depending on the type of purl + """ + url_parts = urlparse(purl) + + subpath = url_parts.fragment if url_parts.fragment != "" else None + qualifiers = url_parts.query if url_parts.query != "" else None + + path = url_parts.path + path = path.split("@") + + if index_in_list(1, path): + version = path[1] + else: + version = None + + path = path[0] + + path = path.split("/") + + name = None + namespace = None + + # pkg:github/TG1999/fetchcode + if index_in_list(2, path): + name = path[2] + namespace = path[1] + + # pkg:crago/clap@2.3.3 + elif index_in_list(1, path): + name = path[1] + + type = path[0] + + converter = { + "bitbucket": build_bitbucket, + "cargo": build_cargo, + "gem": build_gem, + "github": build_github, + "gitlab": build_gitlab, + } + + if type in converter: + url = converter[type]( + name=name, + namespace=namespace, + version=version, + qualifiers=qualifiers, + subpath=subpath, + ) + return url diff --git a/tests/test_purl2url.py b/tests/test_purl2url.py new file mode 100644 index 00000000..bae11119 --- /dev/null +++ b/tests/test_purl2url.py @@ -0,0 +1,49 @@ +# fetchcode is a free software tool from nexB Inc. and others. +# Visit https://github.com/nexB/fetchcode for support and download. +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# http://nexb.com and http://aboutcode.org +# +# This software is licensed under the Apache License version 2.0. +# +# 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. + +from fetchcode.purl2url import purl2url + + +def test_convert_with_purls_string(): + purls_url = { + "pkg:github/tg1999/fetchcode": "https://github.com/tg1999/fetchcode", + "pkg:github/tg1999/fetchcode@master": "https://github.com/tg1999/fetchcode/tree/master", + "pkg:github/tg1999/fetchcode@master#tests": "https://github.com/tg1999/fetchcode/tree/master/tests", + "pkg:github": None, + "pkg:github/tg1999": None, + "pkg:cargo/clap@2.3.3": "https://crates.io/api/v1/crates/clap/2.3.3/download", + "pkg:cargo/rand@0.7.2": "https://crates.io/api/v1/crates/rand/0.7.2/download", + "pkg:cargo/structopt@0.3.11": "https://crates.io/api/v1/crates/structopt/0.3.11/download", + "pkg:cargo/structopt": None, + "pkg:cargo": None, + "pkg:gem/jruby-launcher@1.1.2?platform=java": "https://rubygems.org/downloads/jruby-launcher-1.1.2.gem", + "pkg:gem/ruby-advisory-db-check@0.12.4": "https://rubygems.org/downloads/ruby-advisory-db-check-0.12.4.gem", + "pkg:gem/package-name": None, + "pkg:gem": None, + "pkg:bitbucket/birkenfeld/pygments-main": "https://bitbucket.org/birkenfeld/pygments-main", + "pkg:bitbucket/birkenfeld/pygments-main@244fd47e07d1014f0aed9c": "https://bitbucket.org/birkenfeld/pygments-main/src/244fd47e07d1014f0aed9c", + "pkg:bitbucket/birkenfeld/pygments-main@master#views": "https://bitbucket.org/birkenfeld/pygments-main/src/master/views", + "pkg:bitbucket/birkenfeld": None, + "pkg:bitbucket": None, + "pkg:gitlab/tg1999/firebase@master": "https://gitlab.com/tg1999/firebase/-/tree/master", + "pkg:gitlab/tg1999/firebase@1a122122#views": "https://gitlab.com/tg1999/firebase/-/tree/1a122122/views", + "pkg:gitlab/tg1999/firebase": "https://gitlab.com/tg1999/firebase", + "pkg:gitlab/tg1999": None, + "pkg:gitlab": None, + } + + for purl, url in purls_url.items(): + assert url == purl2url(purl)