Files
Iristyle a000ce8acc feat(ST2.UtilPackages): bump up all packages
- Refresh PackageCache with latest versions of everything
2013-09-16 22:35:46 -04:00

258 lines
8.2 KiB
Python

import re
from ..versions import version_sort, version_filter
from .json_api_client import JSONApiClient
# A predefined list of readme filenames to look for
_readme_filenames = [
'readme',
'readme.txt',
'readme.md',
'readme.mkd',
'readme.mdown',
'readme.markdown',
'readme.textile',
'readme.creole',
'readme.rst'
]
class BitBucketClient(JSONApiClient):
def download_info(self, url):
"""
Retrieve information about downloading a package
:param url:
The URL of the repository, in one of the forms:
https://bitbucket.org/{user}/{repo}
https://bitbucket.org/{user}/{repo}/src/{branch}
https://bitbucket.org/{user}/{repo}/#tags
If the last option, grabs the info from the newest
tag that is a valid semver version.
:raises:
DownloaderException: when there is an error downloading
ClientException: when there is an error parsing the response
:return:
None if no match, False if no commit, or a dict with the following keys:
`version` - the version number of the download
`url` - the download URL of a zip file of the package
`date` - the ISO-8601 timestamp string when the version was published
"""
commit_info = self._commit_info(url)
if not commit_info:
return commit_info
return {
'version': commit_info['version'],
'url': 'https://bitbucket.org/%s/get/%s.zip' % (commit_info['user_repo'], commit_info['commit']),
'date': commit_info['timestamp']
}
def repo_info(self, url):
"""
Retrieve general information about a repository
:param url:
The URL to the repository, in one of the forms:
https://bitbucket.org/{user}/{repo}
https://bitbucket.org/{user}/{repo}/src/{branch}
:raises:
DownloaderException: when there is an error downloading
ClientException: when there is an error parsing the response
:return:
None if no match, or a dict with the following keys:
`name`
`description`
`homepage` - URL of the homepage
`author`
`readme` - URL of the readme
`issues` - URL of bug tracker
`donate` - URL of a donate page
"""
user_repo, branch = self._user_repo_branch(url)
if not user_repo:
return user_repo
api_url = self._make_api_url(user_repo)
info = self.fetch_json(api_url)
issues_url = u'https://bitbucket.org/%s/issues' % user_repo
return {
'name': info['name'],
'description': info['description'] or 'No description provided',
'homepage': info['website'] or url,
'author': info['owner'],
'donate': u'https://www.gittip.com/on/bitbucket/%s/' % info['owner'],
'readme': self._readme_url(user_repo, branch),
'issues': issues_url if info['has_issues'] else None
}
def _commit_info(self, url):
"""
Fetches info about the latest commit to a repository
:param url:
The URL to the repository, in one of the forms:
https://bitbucket.org/{user}/{repo}
https://bitbucket.org/{user}/{repo}/src/{branch}
https://bitbucket.org/{user}/{repo}/#tags
If the last option, grabs the info from the newest
tag that is a valid semver version.
:raises:
DownloaderException: when there is an error downloading
ClientException: when there is an error parsing the response
:return:
None if no match, False if no commit, or a dict with the following keys:
`user_repo` - the user/repo name
`timestamp` - the ISO-8601 UTC timestamp string
`commit` - the branch or tag name
`version` - the extracted version number
"""
tags_match = re.match('https?://bitbucket.org/([^/]+/[^#/]+)/?#tags$', url)
version = None
if tags_match:
user_repo = tags_match.group(1)
tags_url = self._make_api_url(user_repo, '/tags')
tags_list = self.fetch_json(tags_url)
tags = version_filter(tags_list.keys(), self.settings.get('install_prereleases'))
tags = version_sort(tags, reverse=True)
if not tags:
return False
commit = tags[0]
version = re.sub('^v', '', commit)
else:
user_repo, commit = self._user_repo_branch(url)
if not user_repo:
return user_repo
changeset_url = self._make_api_url(user_repo, '/changesets/%s' % commit)
commit_info = self.fetch_json(changeset_url)
commit_date = commit_info['timestamp'][0:19]
if not version:
version = re.sub('[\-: ]', '.', commit_date)
return {
'user_repo': user_repo,
'timestamp': commit_date,
'commit': commit,
'version': version
}
def _main_branch_name(self, user_repo):
"""
Fetch the name of the default branch
:param user_repo:
The user/repo name to get the main branch for
:raises:
DownloaderException: when there is an error downloading
ClientException: when there is an error parsing the response
:return:
The name of the main branch - `master` or `default`
"""
main_branch_url = self._make_api_url(user_repo, '/main-branch')
main_branch_info = self.fetch_json(main_branch_url, True)
return main_branch_info['name']
def _make_api_url(self, user_repo, suffix=''):
"""
Generate a URL for the BitBucket API
:param user_repo:
The user/repo of the repository
:param suffix:
The extra API path info to add to the URL
:return:
The API URL
"""
return 'https://api.bitbucket.org/1.0/repositories/%s%s' % (user_repo, suffix)
def _readme_url(self, user_repo, branch, prefer_cached=False):
"""
Parse the root directory listing for the repo and return the URL
to any file that looks like a readme
:param user_repo:
The user/repo string
:param branch:
The branch to fetch the readme from
:param prefer_cached:
If a cached directory listing should be used instead of a new HTTP request
:raises:
DownloaderException: when there is an error downloading
ClientException: when there is an error parsing the response
:return:
The URL to the readme file, or None
"""
listing_url = self._make_api_url(user_repo, '/src/%s/' % branch)
root_dir_info = self.fetch_json(listing_url, prefer_cached)
for entry in root_dir_info['files']:
if entry['path'].lower() in _readme_filenames:
return 'https://bitbucket.org/%s/raw/%s/%s' % (user_repo,
branch, entry['path'])
return None
def _user_repo_branch(self, url):
"""
Extract the username/repo and branch name from the URL
:param url:
The URL to extract the info from, in one of the forms:
https://bitbucket.org/{user}/{repo}
https://bitbucket.org/{user}/{repo}/src/{branch}
:raises:
DownloaderException: when there is an error downloading
ClientException: when there is an error parsing the response
:return:
A tuple of (user/repo, branch name) or (None, None) if not matching
"""
repo_match = re.match('https?://bitbucket.org/([^/]+/[^/]+)/?$', url)
branch_match = re.match('https?://bitbucket.org/([^/]+/[^/]+)/src/([^/]+)/?$', url)
if repo_match:
user_repo = repo_match.group(1)
branch = self._main_branch_name(user_repo)
elif branch_match:
user_repo = branch_match.group(1)
branch = branch_match.group(2)
else:
return (None, None)
return (user_repo, branch)