Repository: architv/chcli Branch: master Commit: e9e387b9a85c Files: 11 Total size: 14.8 KB Directory structure: gitextract_jrlqgli4/ ├── .gitignore ├── LICENSE ├── README.md ├── challenges/ │ ├── __init__.py │ ├── cli.py │ ├── local_exceptions.py │ ├── platformids.py │ ├── utilities.py │ └── writers.py ├── requirements.txt └── setup.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.pyc challenges/*.pyc config.py README README.txt # Distribution / packaging .Python env/ bin/ build/ develop-eggs/ dist/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # Installer logs pip-log.txt pip-delete-this-directory.txt ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Archit Verma Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ Challenge CLI ===== [![PyPI version](https://badge.fury.io/py/challenge-cli.svg)](https://badge.fury.io/py/challenge-cli) Programming Challenges for Hackers - a CLI for all the programming challenges. ![](http://i.imgur.com/F7BbEH2.png) Install ===== ### Using `pip` ```bash $ pip install challenge-cli ```` ### Build from source ```bash $ git clone git@github.com:architv/chcli.git $ cd chcli $ python setup.py install ``` Usage ==== ### Get active challenges ```bash $ challenges --active $ challenges --active -p HR -p TC # get active challenges from HackerRank(HR) and topcoder(TC). ``` ### Get upcoming challenges ```bash $ challenges --upcoming ``` ### Open a challenge in browser ```bash $ challenges --active 1 # opens the first active challenge in your browser ``` ### Get upcoming challenges from a particular platform ```bash $ challenges --upcoming -p HR -p TC # HR and TC are platform code for HackerRank and TopCoder Respectively ``` ### Get short challenges ```bash $ challenges --short -p CF # get all the short challenges from codeforces ``` ### Get hiring challenges ```bash $ challenges --hiring # get all the hiring challenges ``` ### Get challenges from all platforms with a set time period ```bash $ challenges -t 2 # get all the active challenges and upcoming challenges which start in the next 2 days ``` ### Help ```bash $ challenges --help ``` ### Platform and Platform codes - TC: topcoder.com - HR: hackerrank.com - CF: codeforces.com - HE: hackerearth.com - CC: codechef.com - GCJ: Google Code Jam - KA: kaggle.com For a full list of supported platform and platform codes [see this](challenges/platformids.py). Demo ==== ### Active Challenges ![](http://i.imgur.com/Siedm4R.gif) ### Open a challenge in browser ![](http://i.imgur.com/mxsrc8C.gif) ### Hiring Challenges ![](http://i.imgur.com/c30BEqG.gif) ### Short Challenges from a particular list of platform ![](http://i.imgur.com/SKQgona.png?1) ### Upcoming Challenges within 1 day ![](http://i.imgur.com/3mX7YGh.png) Todo ==== - [ ] Fix alignment issues Licence ==== Open sourced under [MIT License](LICENSE) Support ==== If you like my work, please support the project by donating. - https://gratipay.com/~architv ================================================ FILE: challenges/__init__.py ================================================ ================================================ FILE: challenges/cli.py ================================================ import sys import webbrowser import requests import click from local_exceptions import IncorrectParametersException import writers from platformids import platforms as contest_platforms from utilities import time_difference BASE_URL = "http://challengehuntapp.appspot.com/v2" PLATFORM_IDS = contest_platforms def check_platforms(platforms): """Checks if the platforms have a valid platform code""" if len(platforms) > 0: return all(platform in PLATFORM_IDS for platform in platforms) return True def get_contests_data(): """Gets all the data for all the contests""" req = requests.get(BASE_URL) if req.status_code == requests.codes.ok: return req.json() else: click.secho("Couldn't get the data", fg="red", bold=True) click.secho("Exiting...", fg="red", bold=True) sys.exit() def get_platform_filter(platforms): if platforms: platform_filter = [PLATFORM_IDS[platform] for platform in platforms] else: platform_filter = PLATFORM_IDS.values() return platform_filter def active_contests(platforms): """Gets all the active contests based on time and platforms""" contests_data = get_contests_data() platform_filter = get_platform_filter(platforms) active_challenges = [contest for contest in contests_data["active"] if contest["host_name"] in platform_filter] return active_challenges def upcoming_contests(platforms, time): """Gets all the upcoming contests based on time and platforms""" contests_data = get_contests_data() platform_filter = get_platform_filter(platforms) upcoming_challenges = [contest for contest in contests_data["pending"] if contest["host_name"] in platform_filter and time_difference(contest["start"]).days <= time] return upcoming_challenges def hiring_contests(): """Gets all the hiring challenges from all the availbale platforms""" contests_data = get_contests_data() active_contests = contests_data["active"] upcoming_contests = contests_data["pending"] get_challenge_name = lambda x : x.lower().split() hiring_challenges = [contest for contest in active_contests if "hiring" in get_challenge_name(contest["contest_name"])] hiring_challenges += [contest for contest in upcoming_contests if "hiring" in get_challenge_name(contest["contest_name"])] return hiring_challenges def short_contests(platforms): """Gets all the short contests(less than or equal to 4 hours of duration)""" contests_data = get_contests_data() active_contests = contests_data["active"] upcoming_contests = contests_data["pending"] platform_filter = get_platform_filter(platforms) get_challenge_duration = lambda x : int(x.split(":")[0]) if "days" not in x else float("inf") short_contests = [contest for contest in active_contests if get_challenge_duration(contest["duration"]) <= 4 and contest["host_name"] in platform_filter] short_contests += [contest for contest in upcoming_contests if get_challenge_duration(contest["duration"]) <= 4 and contest["host_name"] in platform_filter] return short_contests def get_all_contests(platforms, time): """Gets all the contests and writes it to standard output""" contests_data = get_contests_data() active_contests = contests_data["active"] upcoming_contests = contests_data["pending"] platform_filter = get_platform_filter(platforms) contests_data = [contest for contest in active_contests if contest["host_name"] in platform_filter] contests_data += [contest for contest in upcoming_contests if contest["host_name"] in platform_filter and time_difference(contest["start"]).days <= time] return contests_data @click.command() @click.option('--active', is_flag=True, help="Shows all the active contests") @click.option('--upcoming', is_flag=True, help="Shows all the upcoming contests") @click.option('--hiring', is_flag=True, help="Shows all the hiring contests") @click.option('--short', is_flag=True, help="Shows all the short contests") @click.option('--platforms', '-p', multiple=True, help=("Choose the platform whose contests you want to see. " "See platform codes for more info")) @click.argument('goto', nargs=1, required=False, type=click.INT) @click.option('--time', '-t', default=6, help="The number of days in the past for which you want to see the contests") def main(active, upcoming, hiring, short, goto, platforms, time): """A CLI for active and upcoming programming challenges from various platforms""" if not check_platforms(platforms): raise IncorrectParametersException('Invlaid code for platform. Please check the platform ids') try: if active: active_challenges = active_contests(platforms) if goto: webbrowser.open(active_challenges[goto - 1]["contest_url"], new=2) else: writers.write_contests(active_challenges, "active") return if upcoming: upcoming_challenges = upcoming_contests(platforms, time) if goto: goto = int(goto) webbrowser.open(upcoming_challenges[goto - 1]["contest_url"], new=2) else: writers.write_contests(upcoming_challenges, "upcoming") return if hiring: hiring_challenges = hiring_contests() if goto: webbrowser.open(hiring_challenges[goto - 1]["contest_url"], new=2) else: writers.write_contests(hiring_challenges, "hiring") return if short: short_challenges = short_contests(platforms) if goto: goto = int(goto) webbrowser.open(short_challenges[goto - 1]["contest_url"], new=2) else: writers.write_contests(short_challenges, "short") return all_contests = get_all_contests(platforms, time) if goto: webbrowser.open(all_contests[goto - 1]["contest_url"], new=2) else: writers.write_contests(all_contests, "all") except IncorrectParametersException as e: click.secho(e.message, fg="red", bold=True) if __name__ == '__main__': main() ================================================ FILE: challenges/local_exceptions.py ================================================ class IncorrectParametersException(Exception): pass ================================================ FILE: challenges/platformids.py ================================================ platforms = { "UA": "usaco.org", "CC": "codechef.com", "EOL":"e-olimp.com", "CH24": "ch24.org", "HR": "hackerrank.com", "HE": "hackerearth.com", "ICPC": "icfpcontest.org", "GCJ": "google.com/codejam", "DE24": "deadline24.pl", "IOI": "stats.ioinformatics.org", "PE": "projecteuler.net", "SN": "contests.snarknews.info", "CG": "codingame.com", "CF": "codeforces.com", "ELY": "e-olymp.com", "DMOJ": "dmoj.ca", "MARA": "marathon24.com", "IPSC": "ipsc.ksp.sk", "UVA": "uva.onlinejudge.org", "OPEN": "opener.itransition.com", "HSIN": "hsin.hr/coci", "CFT": "ctftime.org", "KA": "kaggle.com", "TC": "topcoder.com", "FBHC": "facebook.com/hackercup" } ================================================ FILE: challenges/utilities.py ================================================ from datetime import datetime, timedelta from time import strptime from collections import namedtuple def format_date(date_object): """Formats the date and returns the datetime object""" # date_time = date_object.split("+") return datetime.strptime(str(date_object), "%Y-%m-%dT%H:%M:%S") def time_difference(target_time): """Calculate the difference between the current time and the given time""" TimeDiff = namedtuple("TimeDiff", ["days", "hours", "minutes", "seconds"]) time_diff = format_date(target_time) - datetime.utcnow() hours, remainder = divmod(time_diff.seconds, 3600) minutes, seconds = divmod(remainder, 60) return TimeDiff(days=time_diff.days, hours=hours, minutes=minutes, seconds=seconds) ================================================ FILE: challenges/writers.py ================================================ import json import click from utilities import time_difference def colors(): """Creates an enum for colors""" enums = dict( TIME_LEFT="red", CONTEST_NAME="yellow", HOST="green", MISC="blue", TIME_TO_START="green", ) return type('Enum', (), enums) def challenge(): """Creates an enum for contest type""" enums = dict( ACTIVE="active", UPCOMING="upcoming", HIRING="hiring", ALL="all", SHORT="short", ) return type('Enum', (), enums) def write_contests(contests, contest_type): """Prints the contests based on the parameters passed""" write_contest_header(contest_type) for index, contest in enumerate(contests): time_diff_string = get_time_string(contest, contest_type) contest_name = contest["contest_name"].encode('utf-8') click.echo() click.secho("%-3s" % str(index+1), nl=False, bold=True) click.secho(" %-50s" % contest_name, nl=False, fg=colors().CONTEST_NAME, bold=True) click.secho(" %-20s" % time_diff_string, nl=False, fg=colors().TIME_TO_START, bold=True) click.secho(" %-11s" % str(contest["duration"]), nl=False, bold=True) click.secho(" %-15s" % contest["host_name"], fg=colors().HOST, bold=True) def get_time_string(contest, contest_type): """Return a string with time for the contest to begin/end""" if contest_type == challenge().ACTIVE: time_diff = time_difference(contest["end"]) elif contest_type == challenge().UPCOMING: time_diff = time_difference(contest["start"]) elif contest_type in [challenge().HIRING, challenge().SHORT, challenge().ALL]: try: time_diff = time_difference(contest["start"]) except: time_diff = time_difference(contest["end"]) time_diff_string = "" if time_diff.days > 0: time_diff_string = "{0} days {1} hours".format(time_diff.days, time_diff.hours) elif time_diff.hours > 0: time_diff_string = "{0} hours {1} minutes".format(time_diff.hours, time_diff.minutes) else: time_diff_string = "{0} minutes".format(time_diff.minutes) return time_diff_string def write_contest_header(contest_type): """Prints the header for the type of contest""" if contest_type == challenge().ACTIVE: click.secho("%-3s %-50s %-20s %-11s %-15s" % ("NO.", "NAME", "ENDS IN", "DURATION", "PLATFORM")) elif contest_type == challenge().UPCOMING: click.secho("%-3s %-50s %-20s %-11s %-15s" % ("NO.", "NAME", "STARTS IN", "DURATION", "PLATFORM")) elif contest_type in [challenge().HIRING, challenge().SHORT, challenge().ALL]: click.secho("%-3s %-50s %-20s %-11s %-15s" % ("NO.", "NAME", "STARTS/ENDS IN", "DURATION", "PLATFORM")) ================================================ FILE: requirements.txt ================================================ argparse==1.2.1 click==5.1 requests==2.7.0 wsgiref==0.1.2 ================================================ FILE: setup.py ================================================ #!/usr/bin/env python from setuptools import setup import sys setup( name='challenge-cli', version='0.0.1.0', description='Programming challenges for hackers', author='Archit Verma', license='MIT', classifiers=[ # How mature is this project? Common values are # 3 - Alpha # 4 - Beta # 5 - Production/Stablegit 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', 'Topic :: Software Development :: Build Tools', 'License :: OSI Approved :: MIT License', # Specify the Python versions you support here. In particular, ensure # that you indicate whether you support Python 2, Python 3 or both. 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', ], keywords = "programming challenges competitive topcoder codeforces tool cli", author_email='architv07@gmail.com', url='https://github.com/architv/chcli', packages=['challenges'], install_requires=[ "click>=5.0", "requests==2.7.0" ] + (["colorama==0.3.3"] if "win" in sys.platform else []), entry_points = { 'console_scripts': [ 'challenges = challenges.cli:main' ], } )