Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions fetchcode/purl2url/__init__.py
Original file line number Diff line number Diff line change
@@ -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):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not create a function for something so trivial, also in general using indexes on a list is often a sign that there must be a better way to do the work.

return index < len(list)


def build_bitbucket(**data):
"""
Take dictionary `data` as input and returns a valid bitbucket URL string `url`

@pombredanne pombredanne Jul 1, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use imperative style for docstrings.
Return a Bitbucket URL from a mapping of Package URL data

But there is a larger problem... why use a dict here rather than the packageurl library and object that has nice named attributes?
Also should this be code that lives in the packageurl library instead?

"""

name = data["name"]
namespace = data["namespace"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use single quotes throughout unless this is a docstring

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran black, due to which all these single quotes converted into double quotes, apologies for that 😅

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's great then. Stick to black and make it part of the CI and tests too.

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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some descriptive docstring for this function

"""
Take PackageURL `purl` as input and return a valid URL string `url` depending on the type of purl
"""
url_parts = urlparse(purl)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why parsing a purl yourself? please use the packageurl library instead


subpath = url_parts.fragment if url_parts.fragment != "" else None
qualifiers = url_parts.query if url_parts.query != "" else None

path = url_parts.path

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a more descriptive name for path here? what kind of path is it?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah sure, agreed

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]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the code above is parsing a purl which already exists in a tested and more robust form in the packaeurl library. Please use that library instead.

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
49 changes: 49 additions & 0 deletions tests/test_purl2url.py
Original file line number Diff line number Diff line change
@@ -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():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

purl2url should be part of the packageurl library IMHO

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)