diff --git a/.gitignore b/.gitignore index 929e263..4233d24 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Postinstall bineries +/bot_z/bin/* + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/bot_z/bot_z.py b/bot_z/bot_z.py index f4b46dd..6bf0dcd 100644 --- a/bot_z/bot_z.py +++ b/bot_z/bot_z.py @@ -8,12 +8,13 @@ service. It exposes methods to login, logout and check in and out. from datetime import datetime, timedelta import logging import os +import pkg_resources import time import typing as T from urllib.parse import urlparse os.environ['PATH'] = os.environ['PATH'] + \ - ':' + os.path.join(os.path.abspath(os.path.curdir), 'bin') + ':' + pkg_resources.resource_filename(__name__, 'bin') from selenium import webdriver as wd from selenium.common.exceptions import WebDriverException, NoSuchElementException diff --git a/setup.py b/setup.py index b6af45a..b91a946 100644 --- a/setup.py +++ b/setup.py @@ -1,39 +1,193 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- """The setup script.""" +from collections import namedtuple +from html.parser import HTMLParser +import os +from urllib.error import HTTPError, URLError +from urllib.request import urlopen +import pkg_resources from setuptools import setup, find_packages +from setuptools.command.develop import develop +from setuptools.command.install import install +import sys +import tarfile +import zipfile -with open('README.rst') as readme_file: + +GECKO_RELEASE_PATH = "https://github.com/mozilla/geckodriver" +PKG_NAME = 'bot_z' +VERSION = '0.1.0' +AUTHOR = 'blallo' +AUTHOR_EMAIL = 'blallo@autistici.org' + +with open('README.md') as readme_file: readme = readme_file.read() -with open('HISTORY.rst') as history_file: - history = history_file.read() - requirements = [ 'Click>=6.0', 'selenium>=3.141.0', - # TODO: put package requirements here ] setup_requirements = [ - # TODO(lbarcaroli): put setup requirements (distutils extensions, etc.) here ] test_requirements = [ - # TODO: put package test requirements here ] + +class GitTags(HTMLParser): + tags = list() + take_next = 0 + + def handle_starttag(self, tag, attrs): + dattrs = dict(attrs) + if 'commit-title' in dattrs.get('class', ''): + self.take_next = 1 + + def handle_data(self, data): + if self.take_next == 0: + return + elif self.take_next == 1: + self.take_next = 2 + elif self.take_next == 2: + self.tags.append(data.strip('\n').strip(' ').strip('\n')) + self.take_next = 0 + + +def retrieve_page(url: str) -> bytes: + """ + Auxiliary function to download html body + from and URI, handling the errors. + """ + try: + content = urlopen(url).read() + except HTTPError as e: + print("Connection error: {!s}".format(e)) + raise + except URLError as e: + print("Check the URI: {!s}".format(e)) + raise + + return content + + +def find_latest_version(url: str) -> str: + """ + Retrieves latest geckodriver tag. + """ + tag_page = retrieve_page('{}/tags'.format(url)) + gt = GitTags() + gt.feed(tag_page.decode('utf-8')) + gt.tags.sort() + return gt.tags[-1] + + +def verify_if_superuser() -> bool: + """ + Checks if uid or euid is 0. + """ + _uid = os.getuid() + _euid = os.geteuid() + return _uid == 0 or _euid == 0 + + +def create_local_folder() -> None: + """ + Create a bin/ folder in the current package installation path. + """ + bin_path = pkg_resources.resource_filename(PKG_NAME, BIN_PATH) + + +def assemble_driver_uri() -> str: + """ + Selects the right geckodriver URI. + """ + # TODO: use pkg_resources.get_platform() + latest_vers = find_latest_version(GECKO_RELEASE_PATH) + platform = sys.platform + is_64bits = sys.maxsize > 2**32 + if is_64bits: + full_platform = '{}64'.format(platform) + else: + full_platform = '{}64'.format(platform) + if 'win' in platform: + ext = 'zip' + else: + ext = 'tar.gz' + return '{base}/releases/download/{vers}/geckodriver-{vers}-{platform}.{ext}'.format( + base=GECKO_RELEASE_PATH, + vers=latest_vers, + platform=full_platform, + ext=ext + ) + + +def download_driver_bin(uri: str, path: str) -> None: + """ + Donwloads the geckodriver binary. + """ + name = uri.split('/')[-1] + filepath = os.path.join(path, name) + print("[DRIVER] downloading '{}' to {}".format(uri, filepath)) + content = retrieve_page(uri) + try: + with open(filepath, 'wb') as f: + f.write(content) + if name.endswith(".zip"): + with zipfile.ZipFile(filepath, 'r') as z: + z.extractall(path) + elif name.endswith(".tar.gz"): + with tarfile.open(filepath, 'r') as r: + r.extractall(path) + finally: + os.remove(filepath) + + +def postinstall() -> None: + """ + Performs all the postintallation flow, donwloading in the + right place the geckodriver binary. + """ + # target_path = os.path.join(os.path.abspath(os.path.curdir), 'bot_z', 'bin') + target_path = pkg_resources.resource_filename('bot_z', 'bin') + pkg_resources.ensure_directory(os.path.join(target_path, 'target')) + gecko_uri = assemble_driver_uri() + print("[POSTINSTALL] gecko_uri: {}".format(gecko_uri)) + download_driver_bin(gecko_uri, target_path) + + +# From: https://stackoverflow.com/a/36902139 +class PostDevelopCommand(develop): + """Post-installation for development mode.""" + def run(self): + super().run() + print("POSTINSTALL") + postinstall() + + +class PostInstallCommand(install): + """Post-installation for installation mode.""" + def run(self): + super().run() + postinstall() + + setup( - name='bot_z', - version='0.1.0', + name=PKG_NAME, + version=VERSION, description="A bot to easen the daily routine with zucchetti virtual badge.", - long_description=readme + '\n\n' + history, - author="Leonardo Barcaroli", - author_email='leonardo.barcaroli@deustechnology.com', - url='https://github.com/lbarcaroli/bot_z', + long_description=readme, + author=AUTHOR, + author_email=AUTHOR_EMAIL, + url='https://git.abbiamoundominio.org/blallo/BotZ', packages=find_packages(include=['bot_z']), + cmdclass={ + 'develop': PostDevelopCommand, + 'install': PostInstallCommand, + }, entry_points={ 'console_scripts': [ 'bot_z=bot_z.cli:main' @@ -41,7 +195,7 @@ setup( }, include_package_data=True, install_requires=requirements, - license="GNU General Public License v3", + license="GLWTS Public Licence", zip_safe=False, keywords='bot_z', classifiers=[