pax_global_header00006660000000000000000000000064134456311340014516gustar00rootroot0000000000000052 comment=9a271c030b5aa7ec6dd218b7847270d1f870673d nvchecker-1.4.3/000077500000000000000000000000001344563113400134735ustar00rootroot00000000000000nvchecker-1.4.3/.gitignore000066400000000000000000000001401344563113400154560ustar00rootroot00000000000000records/ *.egg-info/ __pycache__/ /build/ .cache/ .eggs/ *.pyc *.pyo .travis.pub .pytest_cache/ nvchecker-1.4.3/.travis.yml000066400000000000000000000036601344563113400156110ustar00rootroot00000000000000sudo: false dist: xenial language: python cache: pip python: - "3.5" - "3.6" - "3.7" - "nightly" - "pypy3.5" install: pip install -U $DEPS "pytest<4.1" pytest-asyncio pytest-xdist flaky structlog script: pytest env: global: # github - secure: "JNuxbHbO+Qj88r0So+FKp8GBVmobGlBNi0hkZIyOH4cBXtuiM1Jo6FtRYInfTUH5TcgfMQml1a8p9g8n1fbRcTsxPt3kkT0ZleW1fJNudOHJFOmDooM4gC2/A+6aMl3xdnLCQ9cXxqsXjIUBie3GhqC4ufInU7VshxOn7KZADbI3zDuLuw9gdsBQf/OADY4oO3y1URxdnWjssP8pwfDFRSEkuLKNDtsYrhkmp3jRAq5DMtMXTEyHly9CJHow7yMyoBHa6Q/J7+C57pI4JsO8c0nJWy/wQUnqw9EeLE/9gAHY1sHlEpjZtJrV45kRd+KC6x4FtoFjvngxymK2A0zmecBI3DRTWBAZedPPVatAD9nlDmwAacBtwvuZJkt6fMUBWMY1I1NEiwdYxceBiqrnvU48FfNOylXE6KuarCQZik/VWk8olIQjXIukMu8EQ58pnEuLZB7wbwNzMLheomuVMEK1nfLOltKaytztl/7cKlsx6SmxY5rQI/x7QInd+rq9OxDDwCo+jEofPKvAcCbUJj6SqfB7QAUxJwwD/ER4/Bji9KSz3BoCu+x7h/ILcskNqLlg4LDCcpxqMOyxePk7A30sSop1E5YLWo0lmS9s88mEz89tzCWSDVIzwQrdMghNBe6JFMzOoKDRDhEkMrs3MAK+FUJkbteGhHrdC86EidU=" # gitlab - secure: "ZmD5E59cLbGylhId+uYsuaM7HCcuP7E3DVZUHtSKQ7ZtiDIPG2EFCl+WlcPBS2JhdyEJ1v7PbxSCq6/jkSK2EGVcWaHMDIyZRu8TFY+l8mqghHi18l0jeegE7FSqkW2JMWmB3y6jsv7EV6YffrSuvHiNgHyZhhIRkbV/B4N9KvJwNYoyVxGYGoJRe5yuvE+2Xkwc9y0ddxh/p+nILU+Vt0i3Z+yVfg4jul7zN1KhK8I8ax4wpAq+0V1PpWbF6/4UK5Xc/1UMEyWE0f8aEzn4kdC6UetOKacWycq7ag3W1vWKVYJvXyRKjGWmef+Al7z8KbwBkU6KR0Hb2OZWKF3SsCvv8bQEjbqcIeGKQT9J2LTgqTxgFtCMmKWXM3BxLASac/WEdQyyZmQq3XHI/OyJ/1hsSLCgvpexIueITo68LkOigrRRiobSER6KK1CaA1AQFWnPnEwrC3QCtzYUIHPT70nTy1Dx0PiOeQrG/stUoPQ9V0FCBf2tKYg2tUDlJbk7avt8rsmLht1uGx8I75qgg3Di/03N19wEBf6V50H9T23wYXRJ/q2mqPiBCBWIlHwE8NbLZgRfBvCFyUBRvggNAyvZaEOmWRl3U9JEipcqrAQtddzDP1dUbr6SYJChR6yfMkWXXsUvy3FxrOOILeaBT2j0goSaye8aLncUqArgWSw=" matrix: - DEPS=aiohttp - DEPS=tornado pycurl - DEPS=tornado - DEPS="tornado<5" pycurl - DEPS="tornado<5" matrix: fast_finish: true allow_failures: # doesn't work well, see https://travis-ci.org/lilydjwg/nvchecker/jobs/376326582 - python: pypy3.5 env: DEPS=aiohttp nvchecker-1.4.3/LICENSE000066400000000000000000000021131344563113400144750ustar00rootroot00000000000000MIT License Copyright (c) 2013-2017 lilydjwg , et al. 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. nvchecker-1.4.3/README.rst000066400000000000000000000426501344563113400151710ustar00rootroot00000000000000**nvchecker** (short for *new version checker*) is for checking if a new version of some software has been released. .. image:: https://travis-ci.org/lilydjwg/nvchecker.svg :alt: Build Status :target: https://travis-ci.org/lilydjwg/nvchecker .. image:: https://badge.fury.io/py/nvchecker.svg :alt: PyPI version :target: https://badge.fury.io/py/nvchecker | .. image:: https://repology.org/badge/vertical-allrepos/nvchecker.svg :alt: Packaging status :target: https://repology.org/metapackage/nvchecker/versions Contents ======== * `Dependency <#dependency>`_ * `Install and Run <#install-and-run>`_ * `JSON logging <#json-logging>`_ * `Version Record Files <#version-record-files>`_ * `The nvtake Command <#the-nvtake-command>`_ * `Version Source Files <#version-source-files>`_ * `Configuration Section <#configuration-section>`_ * `Global Optons <#global-options>`_ * `Search in a Webpage <#search-in-a-webpage>`_ * `Find with a Command <#find-with-a-command>`_ * `Check AUR <#check-aur>`_ * `Check GitHub <#check-github>`_ * `Check BitBucket <#check-bitbucket>`_ * `Check GitLab <#check-gitlab>`_ * `Check PyPI <#check-pypi>`_ * `Check RubyGems <#check-rubygems>`_ * `Check NPM Registry <#check-npm-registry>`_ * `Check Hackage <#check-hackage>`_ * `Check CPAN <#check-cpan>`_ * `Check Packagist <#check-packagist>`_ * `Check Local Pacman Database <#check-local-pacman-database>`_ * `Check Arch Linux official packages <#check-arch-linux-official-packages>`_ * `Check Debian Linux official packages <#check-debian-linux-official-packages>`_ * `Check Ubuntu Linux official packages <#check-ubuntu-linux-official-packages>`_ * `Check Anitya (release-monitoring.org) <#check-anitya>`_ * `Check Android SDK <#check-android-sdk>`_ * `Manually updating <#manually-updating>`_ * `Version Control System (VCS) (git, hg, svn, bzr) <#version-control-system-vcs-git-hg-svn-bzr>`_ * `Other <#other>`_ * `Bugs <#bugs>`_ Dependency ========== - Python 3.5+ - Python library: structlog - One of these Python library combinations (ordered by preference): * tornado + pycurl * aiohttp * tornado - All commands used in your version source files Install and Run =============== To install:: pip3 install nvchecker To use the latest code, you can also clone this repository and run:: python3 setup.py install To see available options:: nvchecker --help Run with one or more software version source files:: nvchecker source_file You normally will like to specify some "version record files"; see below. JSON logging ------------ With ``--logger=json`` or ``--logger=both``, you can get a structured logging for programmatically consuming. You can use ``--json-log-fd=FD`` to specify the file descriptor to send logs to (take care to do line buffering). The logging level option (``-l`` or ``--logging``) doesn't take effect with this. The JSON log is one JSON string per line. The following documented events and fields are stable, undocumented ones may change without notice. event=updated An update is detected. Fields ``name``, ``old_version`` and ``version`` are available. ``old_version`` maybe ``null``. event=up-to-date There is no update. Fields ``name`` and ``version`` are available. event=no-result No version is detected. There may be an error. Fields ``name`` is available. level=error There is an error. Fields ``name`` and ``exc_info`` may be available to give further information. Version Record Files ==================== Version record files record which version of the software you know or is available. They are simple key-value pairs of ``(name, version)`` separated by a space:: fcitx 4.2.7 google-chrome 27.0.1453.93-200836 vim 7.3.1024 Say you've got a version record file called ``old_ver.txt`` which records all your watched software and their versions, as well as some configuration entries. To update it using ``nvchecker``:: nvchecker source.ini See what are updated with ``nvcmp``:: nvcmp source.ini Manually compare the two files for updates (assuming they are sorted alphabetically; files generated by ``nvchecker`` are already sorted):: comm -13 old_ver.txt new_ver.txt # or say that in English: comm -13 old_ver.txt new_ver.txt | awk '{print $1 " has updated to version " $2 "."}' # show both old and new versions join old_ver.txt new_ver.txt | awk '$2 != $3' The ``nvtake`` Command ---------------------- This command helps to manage version record files. It reads both old and new version record files, and a list of names given on the commandline. It then update the versions of those names in the old version record file. This helps when you have known (and processed) some of the updated software, but not all. You can tell nvchecker that via this command instead of editing the file by hand. This command will help most if you specify where you version record files are in your config file. See below for how to use a config file. Version Source Files ==================== The software version source files are in ini format. *Section names* is the name of the software. Following fields are used to tell nvchecker how to determine the current version of that software. See ``sample_source.ini`` for an example. Configuration Section --------------------- A special section named ``__config__`` is special, it provides some configuration options. Relative path are relative to the source files, and ``~`` and environmental variables are expanded. Currently supported options are: oldver Specify a version record file containing the old version info. newver Specify a version record file to store the new version info. proxy The HTTP proxy to use. The format is ``host:port``, e.g. ``localhost:8087``. max_concurrent Max number of concurrent jobs. Default: 20. keyfile Specify an ini config file containing key (token) information. This file should contain a ``keys`` section, mapping key names to key values. See specific source for the key name(s) to use. Global Options -------------- The following options apply to all check sources. prefix Strip the prefix string if the version string starts with it. Otherwise the version string is returned as-is. from_pattern, to_pattern Both are Python-compatible regular expressions. If ``from_pattern`` is found in the version string, it will be replaced with ``to_pattern``. missing_ok Suppress warnings and errors if a version checking module finds nothing. Currently only ``regex`` supports it. If both ``prefix`` and ``from_pattern``/``to_pattern`` are used, ``from_pattern``/``to_pattern`` are ignored. If you want to strip the prefix and then do something special, just use ``from_pattern```/``to_pattern``. For example, the transformation of ``v1_1_0`` => ``1.1.0`` can be achieved with ``from_pattern = v(\d+)_(\d+)_(\d+)`` and ``to_pattern = \1.\2.\3``. List Options ------------ The following options apply to sources that return a list. See individual source sections to determine whether they are supported. include_regex Only consider version strings that match the given regex. The whole string should match the regex. Be sure to use ``.*`` when you mean it! exclude_regex Don't consider version strings that match the given regex. The whole string should match the regex. Be sure to use ``.*`` when you mean it! This option has higher precedence that ``include_regex``; that is, if matched by this one, it's excluded even it's also matched by ``include_regex``. sort_version_key Sort the version string using this key function. Choose between ``parse_version`` and ``vercmp``. Default value is ``parse_version``. ``parse_version`` use ``pkg_resources.parse_version``. ``vercmp`` use ``pyalpm.vercmp``. ignored Version strings that are explicitly ignored, separated by whitespace. This can be useful to avoid some known mis-named versions, so newer ones won't be "overridden" by the old broken ones. Search in a Webpage ------------------- Search through a specific webpage for the version string. This type of version finding has these fields: url The URL of the webpage to fetch. encoding (*Optional*) The character encoding of the webpage, if ``latin1`` is not appropriate. regex A regular expression used to find the version string. It can have zero or one capture group. The capture group or the whole match is the version string. When multiple version strings are found, the maximum of those is chosen. proxy The HTTP proxy to use. The format is ``host:port``, e.g. ``localhost:8087``. user_agent The ``User-Agent`` header value to use. Use something more like a tool (e.g. ``curl/7.40.0``) in Europe or the real web page won't get through because cookie policies (SourceForge has this issue). sort_version_key Sort the version string using this key function. Choose between ``parse_version`` and ``vercmp``. Default value is ``parse_version``. ``parse_version`` use ``pkg_resources.parse_version``. ``vercmp`` use ``pyalpm.vercmp``. This source supports `list options`_. Find with a Command ------------------- Use a shell command line to get the version. The output is striped first, so trailing newlines do not bother. cmd The command line to use. This will run with the system's standard shell (i.e. ``/bin/sh``). Check AUR --------- Check `Arch User Repository `_ for updates. aur The package name in AUR. If empty, use the name of software (the *section name*). strip-release Strip the release part. use_last_modified Append last modified time to the version. Check GitHub ------------ Check `GitHub `_ for updates. The version returned is in date format ``%Y%m%d``, e.g. ``20130701``. github The github repository, with author, e.g. ``lilydjwg/nvchecker``. branch Which branch to track? Default: ``master``. path Only commits containing this file path will be returned. use_latest_release Set this to ``true`` to check for the latest release on GitHub. An annotated tag creates a "release" on GitHub. It's not the same with git tags, which includes both annotated tags and lightweight ones. use_max_tag Set this to ``true`` to check for the max tag on GitHub. Unlike ``use_latest_release``, this option includes both annotated tags and lightweight ones, and return the largest one sorted by the ``sort_version_key`` option. max_page How many pages do we search for the max tag? Default is 1. This works when ``use_max_tag`` is set. However, with current API in use, GitHub seems to always return all data in one page, making this option obsolete. proxy The HTTP proxy to use. The format is ``host:port``, e.g. ``localhost:8087``. include_tags_pattern, ignored_tags, sort_version_key Deprecated. Use `list options`_ instead. An environment variable ``NVCHECKER_GITHUB_TOKEN`` or a key named ``github`` can be set to a GitHub OAuth token in order to request more frequently than anonymously. This source supports `list options`_ when ``use_max_tag`` is set. Check BitBucket --------------- Check `BitBucket `_ for updates. The version returned is in date format ``%Y%m%d``, e.g. ``20130701``. bitbucket The bitbucket repository, with author, e.g. ``lilydjwg/dotvim``. branch Which branch to track? Default is the repository's default. use_max_tag Set this to ``true`` to check for the max tag on BitBucket. Will return the biggest one sorted by ``pkg_resources.parse_version``. ignored_tags, sort_version_key Deprecated. Use `list options`_ instead. This source supports `list options`_ when ``use_max_tag`` is set. Check GitLab ------------- Check `GitLab `_ for updates. The version returned is in date format ``%Y%m%d``, e.g. ``20130701``. gitlab The gitlab repository, with author, e.g. ``Deepin/deepin-music``. branch Which branch to track? Default: ``master``. use_max_tag Set this to ``true`` to check for the max tag on BitBucket. Will return the biggest one sorted by ``pkg_resources.parse_version``. host Hostname for self-hosted GitLab instance. token GitLab authorization token used to call the API. ignored_tags, sort_version_key Deprecated. Use `list options`_ instead. To set an authorization token, you can set: - a key named ``gitlab_{host}`` in the keyfile (where ``host`` is formed the same as the environment variable, but all lowercased). - an environment variable ``NVCHECKER_GITLAB_TOKEN_{host}`` must provide that token. The ``host`` part is the uppercased version of the ``host`` setting, with dots (``.``) and slashes (``/``) replaced by underscores (``_``), e.g. ``NVCHECKER_GITLAB_TOKEN_GITLAB_COM``. - the token option This source supports `list options`_ when ``use_max_tag`` is set. Check PyPI ---------- Check `PyPI `_ for updates. pypi The name used on PyPI, e.g. ``PySide``. use_pre_release Whether to accept pre release. Default is false. Check RubyGems -------------- Check `RubyGems `_ for updates. gems The name used on RubyGems, e.g. ``sass``. Check NPM Registry ------------------ Check `NPM Registry `_ for updates. npm The name used on NPM Registry, e.g. ``coffee-script``. Check Hackage ------------- Check `Hackage `_ for updates. hackage The name used on Hackage, e.g. ``pandoc``. Check CPAN -------------- Check `MetaCPAN `_ for updates. cpan The name used on CPAN, e.g. ``YAML``. proxy The HTTP proxy to use. The format is ``host:port``, e.g. ``localhost:8087``. Check Packagist --------------- Check `Packagist `_ for updates. packagist The name used on Packagist, e.g. ``monolog/monolog``. Check Local Pacman Database --------------------------- This is used when you run ``nvchecker`` on an Arch Linux system and the program always keeps up with a package in your configured repositories for `Pacman`_. pacman The package name to reference to. strip-release Strip the release part. Check Arch Linux official packages ---------------------------------- This enables you to track the update of `Arch Linux official packages `_, without needing of pacman and an updated local Pacman databases. archpkg Name of the Arch Linux package. strip-release Strip the release part. Check Debian Linux official packages ------------------------------------ This enables you to track the update of `Debian Linux official packages `_, without needing of apt and an updated local APT database. debianpkg Name of the Debian Linux source package. suite Name of the Debian release (jessie, wheezy, etc, defaults to sid) strip-release Strip the release part. Check Ubuntu Linux official packages ------------------------------------ This enables you to track the update of `Ubuntu Linux official packages `_, without needing of apt and an updated local APT database. ubuntupkg Name of the Ubuntu Linux source package. suite Name of the Ubuntu release (xenial, zesty, etc, defaults to None, which means no limit on suite) strip-release Strip the release part. Check Anitya ------------ This enables you to track updates from `Anitya `_ (release-monitoring.org). anitya ``distro/package``, where ``distro`` can be a lot of things like "fedora", "arch linux", "gentoo", etc. ``package`` is the package name of the chosen distribution. Check Android SDK ----------------- This enables you to track updates of Android SDK packages listed in ``sdkmanager --list``. android_sdk The package path prefix. This value is matched against the ``path`` attribute in all nodes in an SDK manifest XML. The first match is used for version comparisons. repo Should be one of ``addon`` or ``package``. Packages in ``addon2-1.xml`` use ``addon`` and packages in ``repository2-1.xml`` use ``package``. Manually updating ----------------- This enables you to manually specify the version (maybe because you want to approve each release before it gets to the script). manual The version string. Version Control System (VCS) (git, hg, svn, bzr) ------------------------------------------------ Check a VCS repo for new commits. The version returned is currently not related to the version of the software and will increase whenever the referred VCS branch changes. This is mainly for Arch Linux. vcs The url of the remote VCS repo, using the same syntax with a VCS url in PKGBUILD (`Pacman`_'s build script). The first VCS url found in the source array of the PKGBUILD will be used if this is left blank. (Note: for a blank ``vcs`` setting to work correctly, the PKGBUILD has to be in a directory with the name of the software under the path where nvchecker is run. Also, all the commands, if any, needed when sourcing the PKGBUILD need to be installed). use_max_tag Set this to ``true`` to check for the max tag. Currently only supported for ``git``. This option returns the biggest tag sorted by ``pkg_resources.parse_version``. ignored_tags Ignore certain tags while computing the max tag. Tags are separate by whitespaces. This option must be used together with ``use_max_tag``. This can be useful to avoid some known badly versioned tags, so the newer tags won't be "overridden" by the old broken ones. Other ----- More to come. Send me a patch or pull request if you can't wait and have written one yourself :-) Bugs ==== * Finish writing results even on Ctrl-C or other interruption. .. _Pacman: https://wiki.archlinux.org/index.php/Pacman .. _list options: #list-options nvchecker-1.4.3/nvchecker/000077500000000000000000000000001344563113400154435ustar00rootroot00000000000000nvchecker-1.4.3/nvchecker/__init__.py000066400000000000000000000001461344563113400175550ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2019 lilydjwg , et al. __version__ = '1.4.3' nvchecker-1.4.3/nvchecker/core.py000066400000000000000000000153261344563113400167540ustar00rootroot00000000000000# vim: se sw=2: # MIT licensed # Copyright (c) 2013-2018 lilydjwg , et al. import os import sys import configparser import asyncio import logging import structlog from .lib import nicelogger from .get_version import get_version from .source import session from . import slogconf from . import __version__ logger = structlog.get_logger(logger_name=__name__) def add_common_arguments(parser): parser.add_argument('-l', '--logging', choices=('debug', 'info', 'warning', 'error'), default='info', help='logging level (default: info)') parser.add_argument('--logger', default='pretty', choices=['pretty', 'json', 'both'], help='select which logger to use') parser.add_argument('--json-log-fd', metavar='FD', type=lambda fd: os.fdopen(int(fd), mode='w'), help='specify fd to send json logs to. stdout by default') parser.add_argument('-V', '--version', action='store_true', help='show version and exit') parser.add_argument('file', metavar='FILE', nargs='?', type=open, help='software version source file') def process_common_arguments(args): '''return True if should stop''' processors = [ slogconf.exc_info, ] logger_factory = None if args.logger in ['pretty', 'both']: slogconf.fix_logging() nicelogger.enable_pretty_logging( getattr(logging, args.logging.upper())) processors.append(slogconf.stdlib_renderer) if args.logger == 'pretty': logger_factory=structlog.PrintLoggerFactory( file=open(os.devnull, 'w'), ) processors.append(slogconf.null_renderer) if args.logger in ['json', 'both']: processors.extend([ structlog.processors.format_exc_info, slogconf.json_renderer, ]) if logger_factory is None: logfile = args.json_log_fd or sys.stdout logger_factory = structlog.PrintLoggerFactory(file=logfile) structlog.configure( processors = processors, logger_factory = logger_factory, ) if args.version: progname = os.path.basename(sys.argv[0]) print('%s v%s' % (progname, __version__)) return True def safe_overwrite(fname, data, *, method='write', mode='w', encoding=None): # FIXME: directory has no read perm # FIXME: symlinks and hard links tmpname = fname + '.tmp' # if not using "with", write can fail without exception with open(tmpname, mode, encoding=encoding) as f: getattr(f, method)(data) # see also: https://thunk.org/tytso/blog/2009/03/15/dont-fear-the-fsync/ f.flush() os.fsync(f.fileno()) # if the above write failed (because disk is full etc), the old data should be kept os.rename(tmpname, fname) def read_verfile(file): v = {} try: with open(file) as f: for l in f: name, ver = l.rstrip().split(None, 1) v[name] = ver except FileNotFoundError: pass return v def write_verfile(file, versions): # sort using only alphanums, as done by the sort command, and needed by # comm command data = ['%s %s\n' % item for item in sorted(versions.items(), key=lambda i: (''.join(filter(str.isalnum, i[0])), i[1]))] safe_overwrite(file, data, method='writelines') class Source: oldver = newver = None tries = 1 def __init__(self, file, tries=1): self.config = config = configparser.ConfigParser( dict_type=dict, allow_no_value=True ) self.name = file.name self.tries = tries config.read_file(file) if '__config__' in config: c = config['__config__'] d = os.path.dirname(file.name) if 'oldver' in c and 'newver' in c: self.oldver = os.path.expandvars(os.path.expanduser( os.path.join(d, c.get('oldver')))) self.newver = os.path.expandvars(os.path.expanduser( os.path.join(d, c.get('newver')))) keyfile = c.get('keyfile') if keyfile: keyfile = os.path.expandvars(os.path.expanduser( os.path.join(d, c.get('keyfile')))) self.max_concurrent = c.getint('max_concurrent', 20) self.keymanager = KeyManager(keyfile) session.nv_config = config["__config__"] else: self.max_concurrent = 20 self.keymanager = KeyManager(None) async def check(self): if self.oldver: self.oldvers = read_verfile(self.oldver) else: self.oldvers = {} self.curvers = self.oldvers.copy() tries = self.tries token_q = asyncio.Queue(maxsize=self.max_concurrent) for _ in range(self.max_concurrent): await token_q.put(True) async def worker(name, conf): await token_q.get() try: for i in range(tries): try: ret = await get_version( name, conf, keyman=self.keymanager) return name, ret except Exception as e: if i + 1 < tries: logger.warning('failed, retrying', name=name, exc_info=e) await asyncio.sleep(i) else: return name, e finally: await token_q.put(True) config = self.config futures = [] for name in config.sections(): if name == '__config__': continue conf = config[name] conf['oldver'] = self.oldvers.get(name, None) fu = asyncio.ensure_future(worker(name, conf)) futures.append(fu) for fu in asyncio.as_completed(futures): name, result = await fu if isinstance(result, Exception): logger.error('unexpected error happened', name=name, exc_info=result) self.on_exception(name, result) elif result is not None: self.print_version_update(name, result) else: conf = config[name] if not conf.getboolean('missing_ok', False): logger.warning('no-result', name=name) self.on_no_result(name) if self.newver: write_verfile(self.newver, self.curvers) def print_version_update(self, name, version): oldver = self.oldvers.get(name, None) if not oldver or oldver != version: logger.info('updated', name=name, version=version, old_version=oldver) self.curvers[name] = version self.on_update(name, version, oldver) else: logger.debug('up-to-date', name=name, version=version) def on_update(self, name, version, oldver): pass def on_no_result(self, name): pass def on_exception(self, name, exc): pass def __repr__(self): return '' % self.name class KeyManager: def __init__(self, file): self.config = config = configparser.ConfigParser(dict_type=dict) if file is not None: config.read([file]) else: config.add_section('keys') def get_key(self, name): return self.config.get('keys', name, fallback=None) nvchecker-1.4.3/nvchecker/get_version.py000066400000000000000000000055501344563113400203460ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import re from importlib import import_module import structlog from .sortversion import sort_version_keys logger = structlog.get_logger(logger_name=__name__) handler_precedence = ( 'github', 'aur', 'pypi', 'archpkg', 'debianpkg', 'ubuntupkg', 'gems', 'pacman', 'cmd', 'bitbucket', 'regex', 'manual', 'vcs', 'cratesio', 'npm', 'hackage', 'cpan', 'gitlab', 'packagist', 'anitya', 'android_sdk', ) def substitute_version(version, name, conf): ''' Substitute the version string via defined rules in the configuration file. See README.rst#global-options for details. ''' prefix = conf.get('prefix') if prefix: if version.startswith(prefix): version = version[len(prefix):] return version from_pattern = conf.get('from_pattern') if from_pattern: to_pattern = conf.get('to_pattern') if not to_pattern: raise ValueError('%s: from_pattern exists but to_pattern doesn\'t', name) return re.sub(from_pattern, to_pattern, version) # No substitution rules found. Just return the original version string. return version def apply_list_options(versions, conf): pattern = conf.get('include_regex') if pattern: pattern = re.compile(pattern) versions = [x for x in versions if pattern.fullmatch(x)] pattern = conf.get('exclude_regex') if pattern: pattern = re.compile(pattern) versions = [x for x in versions if not pattern.fullmatch(x)] ignored = set(conf.get('ignored', '').split()) if ignored: versions = [x for x in versions if x not in ignored] if not versions: return sort_version_key = sort_version_keys[ conf.get("sort_version_key", "parse_version")] versions.sort(key=sort_version_key) return versions[-1] _cache = {} async def get_version(name, conf, **kwargs): for key in handler_precedence: if key in conf: mod = import_module('.source.' + key, __package__) func = mod.get_version get_cacheable_conf = getattr(mod, 'get_cacheable_conf', lambda name, conf: conf) break else: logger.error('no idea to get version info.', name=name) return cacheable_conf = get_cacheable_conf(name, conf) cache_key = tuple(sorted(cacheable_conf.items())) if cache_key in _cache: version = _cache[cache_key] logger.debug('cache hit', name=name, cache_key=cache_key, cached=version) return version version = await func(name, conf, **kwargs) if isinstance(version, list): version = apply_list_options(version, conf) if version: version = version.replace('\n', ' ') try: version = substitute_version(version, name, conf) except (ValueError, re.error): logger.exception('error occurred in version substitutions', name=name) if version is not None: _cache[cache_key] = version return version nvchecker-1.4.3/nvchecker/lib/000077500000000000000000000000001344563113400162115ustar00rootroot00000000000000nvchecker-1.4.3/nvchecker/lib/README.md000066400000000000000000000002061344563113400174660ustar00rootroot00000000000000This directory belongs to modules from my [winterpy](https://github.com/lilydjwg/winterpy) and can be synced from there without care. nvchecker-1.4.3/nvchecker/lib/__init__.py000066400000000000000000000000001344563113400203100ustar00rootroot00000000000000nvchecker-1.4.3/nvchecker/lib/nicelogger.py000066400000000000000000000064101344563113400207020ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. ''' A Tornado-inspired logging formatter, with displayed time with millisecond accuracy FYI: pyftpdlib also has a Tornado-style logger. ''' import sys import time import logging class TornadoLogFormatter(logging.Formatter): def __init__(self, color, *args, **kwargs): super().__init__(*args, **kwargs) self._color = color if color: import curses curses.setupterm() if sys.hexversion < 0x30203f0: fg_color = str(curses.tigetstr("setaf") or curses.tigetstr("setf") or "", "ascii") else: fg_color = curses.tigetstr("setaf") or curses.tigetstr("setf") or b"" self._colors = { logging.DEBUG: str(curses.tparm(fg_color, 4), # Blue "ascii"), logging.INFO: str(curses.tparm(fg_color, 2), # Green "ascii"), logging.WARNING: str(curses.tparm(fg_color, 3), # Yellow "ascii"), logging.ERROR: str(curses.tparm(fg_color, 1), # Red "ascii"), logging.CRITICAL: str(curses.tparm(fg_color, 9), # Bright Red "ascii"), } self._normal = str(curses.tigetstr("sgr0"), "ascii") def format(self, record): try: record.message = record.getMessage() except Exception as e: record.message = "Bad message (%r): %r" % (e, record.__dict__) record.asctime = time.strftime( "%m-%d %H:%M:%S", self.converter(record.created)) record.asctime += '.%03d' % ((record.created % 1) * 1000) prefix = '[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]' % \ record.__dict__ if self._color: prefix = (self._colors.get(record.levelno, self._normal) + prefix + self._normal) formatted = prefix + " " + record.message formatted += ''.join( ' %s=%s' % (k, v) for k, v in record.__dict__.items() if k not in { 'levelname', 'asctime', 'module', 'lineno', 'args', 'message', 'filename', 'exc_info', 'exc_text', 'created', 'funcName', 'processName', 'process', 'msecs', 'relativeCreated', 'thread', 'threadName', 'name', 'levelno', 'msg', 'pathname', 'stack_info', }) if record.exc_info: if not record.exc_text: record.exc_text = self.formatException(record.exc_info) if record.exc_text: formatted = formatted.rstrip() + "\n" + record.exc_text return formatted.replace("\n", "\n ") def enable_pretty_logging(level=logging.DEBUG, handler=None, color=None): ''' handler: specify a handler instead of default StreamHandler color: boolean, force color to be on / off. Default to be on only when ``handler`` isn't specified and the term supports color ''' logger = logging.getLogger() if handler is None: h = logging.StreamHandler() else: h = handler if color is None: color = False if handler is None and sys.stderr.isatty(): try: import curses curses.setupterm() if curses.tigetnum("colors") > 0: color = True except: import traceback traceback.print_exc() formatter = TornadoLogFormatter(color=color) h.setLevel(level) h.setFormatter(formatter) logger.setLevel(level) logger.addHandler(h) nvchecker-1.4.3/nvchecker/lib/notify.py000066400000000000000000000046441344563113400201030ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. ''' 调用 libnotify ''' __all__ = ["set", "show", "update", "set_timeout", "set_urgency"] from ctypes import * from threading import Lock import atexit NOTIFY_URGENCY_LOW = 0 NOTIFY_URGENCY_NORMAL = 1 NOTIFY_URGENCY_CRITICAL = 2 UrgencyLevel = {NOTIFY_URGENCY_LOW, NOTIFY_URGENCY_NORMAL, NOTIFY_URGENCY_CRITICAL} libnotify = None gobj = None libnotify_lock = Lock() libnotify_inited = False class obj: pass notify_st = obj() def set(summary=None, body=None, icon_str=None): with libnotify_lock: init() if summary is not None: notify_st.summary = summary.encode() notify_st.body = notify_st.icon_str = None if body is not None: notify_st.body = body.encode() if icon_str is not None: notify_st.icon_str = icon_str.encode() libnotify.notify_notification_update( notify_st.notify, notify_st.summary, notify_st.body, notify_st.icon_str, ) def show(): libnotify.notify_notification_show(notify_st.notify, c_void_p()) def update(summary=None, body=None, icon_str=None): if not any((summary, body)): raise TypeError('at least one argument please') set(summary, body, icon_str) show() def set_timeout(self, timeout): '''set `timeout' in milliseconds''' libnotify.notify_notification_set_timeout(notify_st.notify, int(timeout)) def set_urgency(self, urgency): if urgency not in UrgencyLevel: raise ValueError libnotify.notify_notification_set_urgency(notify_st.notify, urgency) def init(): global libnotify_inited, libnotify, gobj if libnotify_inited: return try: libnotify = CDLL('libnotify.so') except OSError: libnotify = CDLL('libnotify.so.4') gobj = CDLL('libgobject-2.0.so') libnotify.notify_init('pynotify') libnotify_inited = True libnotify.notify_notification_new.restype = c_void_p notify_st.notify = c_void_p(libnotify.notify_notification_new( c_void_p(), c_void_p(), c_void_p(), )) atexit.register(uninit) def uninit(): global libnotify_inited try: if libnotify_inited: gobj.g_object_unref(notify_st.notify) libnotify.notify_uninit() libnotify_inited = False except AttributeError: # libnotify.so 已被卸载 pass if __name__ == '__main__': from time import sleep notify = __import__('__main__') notify.set('This is a test', '测试一下。') notify.show() sleep(1) notify.update(body='再测试一下。') nvchecker-1.4.3/nvchecker/main.py000077500000000000000000000023201344563113400167410ustar00rootroot00000000000000#!/usr/bin/env python3 # MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import argparse import asyncio import structlog from .lib import notify from . import core logger = structlog.get_logger(logger_name=__name__) notifications = [] args = None class Source(core.Source): def on_update(self, name, version, oldver): if args.notify: msg = '%s updated to version %s' % (name, version) notifications.append(msg) notify.update('nvchecker', '\n'.join(notifications)) def main(): global args parser = argparse.ArgumentParser(description='New version checker for software') parser.add_argument('-n', '--notify', action='store_true', default=False, help='show desktop notifications when a new version is available') parser.add_argument('-t', '--tries', default=1, type=int, metavar='N', help='try N times when errors occur') core.add_common_arguments(parser) args = parser.parse_args() if core.process_common_arguments(args): return if not args.file: return s = Source(args.file, args.tries) ioloop = asyncio.get_event_loop() ioloop.run_until_complete(s.check()) if __name__ == '__main__': main() nvchecker-1.4.3/nvchecker/slogconf.py000066400000000000000000000054411344563113400176330ustar00rootroot00000000000000# vim: se sw=2: # MIT licensed # Copyright (c) 2018 lilydjwg , et al. import logging import os import io import traceback import structlog def _console_msg(event): evt = event['event'] if evt == 'up-to-date': msg = 'up-to-date, version %s' % event['version'] del event['version'] elif evt == 'updated': if event.get('old_version'): msg = 'updated from %(old_version)s to %(version)s' % event else: msg = 'updated to %(version)s' % event del event['version'], event['old_version'] else: msg = evt if 'name' in event: msg = '%s: %s' % (event['name'], msg) del event['name'] event['msg'] = msg return event def exc_info(logger, level, event): if level == 'exception': event['exc_info'] = True return event def stdlib_renderer(logger, level, event): # return event unchanged for further processing std_event = _console_msg(event.copy()) try: logger = logging.getLogger(std_event.pop('logger_name')) except KeyError: logger = logging.getLogger() msg = std_event.pop('msg', std_event.pop('event')) exc_info = std_event.pop('exc_info', None) getattr(logger, level)( msg, exc_info = exc_info, extra=std_event, ) return event _renderer = structlog.processors.JSONRenderer(ensure_ascii=False) def json_renderer(logger, level, event): event['level'] = level return _renderer(logger, level, event) def null_renderer(logger, level, event): return '' class _Logger(logging.Logger): _my_srcfile = os.path.normcase( stdlib_renderer.__code__.co_filename) _structlog_dir = os.path.dirname(structlog.__file__) def findCaller(self, stack_info=False, stacklevel=1): """ Find the stack frame of the caller so that we can note the source file name, line number and function name. """ f = logging.currentframe() #On some versions of IronPython, currentframe() returns None if #IronPython isn't run with -X:Frames. if f is not None: f = f.f_back orig_f = f while f and stacklevel > 1: f = f.f_back stacklevel -= 1 if not f: f = orig_f rv = "(unknown file)", 0, "(unknown function)", None while hasattr(f, "f_code"): co = f.f_code filename = os.path.normcase(co.co_filename) if filename in [logging._srcfile, self._my_srcfile] \ or filename.startswith(self._structlog_dir): f = f.f_back continue sinfo = None if stack_info: sio = io.StringIO() sio.write('Stack (most recent call last):\n') traceback.print_stack(f, file=sio) sinfo = sio.getvalue() if sinfo[-1] == '\n': sinfo = sinfo[:-1] sio.close() rv = (co.co_filename, f.f_lineno, co.co_name, sinfo) break return rv def fix_logging(): logging.setLoggerClass(_Logger) nvchecker-1.4.3/nvchecker/sortversion.py000066400000000000000000000012741344563113400204160ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. ''' Sort versions using pkg_resource.parse_version or pyalpm.vercmp ''' __all__ = ["sort_version_keys"] from functools import cmp_to_key from pkg_resources import parse_version try: import pyalpm vercmp = cmp_to_key(pyalpm.vercmp) except ImportError: def vercmp(k): raise NotImplementedError("Using vercmp but pyalpm can not be imported!") sort_version_keys = {"parse_version": parse_version, "vercmp": vercmp} if __name__ == '__main__': assert(parse_version("v6.0") < parse_version("6.1")) assert(parse_version("v6.0") > parse_version("v6.1-stable")) assert(vercmp("v6.0") < vercmp("v6.1-stable")) nvchecker-1.4.3/nvchecker/source/000077500000000000000000000000001344563113400167435ustar00rootroot00000000000000nvchecker-1.4.3/nvchecker/source/__init__.py000066400000000000000000000012061344563113400210530ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. try: import tornado, pycurl # connection reuse, http/2 which = 'tornado' except ImportError: try: import aiohttp which = 'aiohttp' # connection reuse except ImportError: import tornado which = 'tornado' # fallback m = __import__('%s_httpclient' % which, globals(), locals(), level=1) __all__ = m.__all__ for x in __all__: globals()[x] = getattr(m, x) def conf_cacheable_with_name(key): def get_cacheable_conf(name, conf): conf = dict(conf) conf[key] = conf.get(key) or name return conf return get_cacheable_conf nvchecker-1.4.3/nvchecker/source/aiohttp_httpclient.py000066400000000000000000000017141344563113400232260ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import atexit import asyncio import aiohttp connector = aiohttp.TCPConnector(limit=20) __all__ = ['session', 'HTTPError'] class HTTPError(Exception): def __init__(self, code, message, response): self.code = code self.message = message self.response = response class BetterClientSession(aiohttp.ClientSession): async def _request(self, *args, **kwargs): if hasattr(self, "nv_config") and self.nv_config.get("proxy"): kwargs.setdefault("proxy", self.nv_config.get("proxy")) res = await super(BetterClientSession, self)._request( *args, **kwargs) if res.status >= 400: raise HTTPError(res.status, res.reason, res) return res session = BetterClientSession(connector=connector) @atexit.register def cleanup(): loop = asyncio.get_event_loop() loop.run_until_complete(session.close()) nvchecker-1.4.3/nvchecker/source/android_sdk.py000066400000000000000000000035771344563113400216120ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2017 Yen Chi Hsuan from asyncio.locks import Lock import os import re from xml.etree import ElementTree from . import session _ANDROID_REPO_MANIFESTS = { 'addon': 'https://dl.google.com/android/repository/addon2-1.xml', 'package': 'https://dl.google.com/android/repository/repository2-1.xml', } _repo_manifests_cache = {} _repo_manifests_locks = {} for repo in _ANDROID_REPO_MANIFESTS.keys(): _repo_manifests_locks[repo] = Lock() async def _get_repo_manifest(repo): async with _repo_manifests_locks[repo]: if repo in _repo_manifests_cache: return _repo_manifests_cache[repo] repo_xml_url = _ANDROID_REPO_MANIFESTS[repo] async with session.get(repo_xml_url) as res: data = (await res.read()).decode('utf-8') repo_manifest = ElementTree.fromstring(data) _repo_manifests_cache[repo] = repo_manifest return repo_manifest async def get_version(name, conf, **kwargs): repo = conf['repo'] pkg_path_prefix = conf['android_sdk'] repo_manifest = await _get_repo_manifest(repo) for pkg in repo_manifest.findall('.//remotePackage'): if not pkg.attrib['path'].startswith(pkg_path_prefix): continue for archive in pkg.findall('./archives/archive'): host_os = archive.find('./host-os') if host_os is not None and host_os.text != 'linux': continue archive_url = archive.find('./complete/url').text # revision rev = pkg.find('./revision') rev_strs = [] for part in ('major', 'minor', 'micro'): part_node = rev.find('./' + part) if part_node is not None: rev_strs.append(part_node.text) # release number filename, ext = os.path.splitext(archive_url) rel_str = filename.rsplit('-')[-1] mobj = re.match(r'r\d+', rel_str) if mobj: rev_strs.append(rel_str) return '.'.join(rev_strs) nvchecker-1.4.3/nvchecker/source/anitya.py000066400000000000000000000006461344563113400206100ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2017 lilydjwg , et al. import structlog from . import session logger = structlog.get_logger(logger_name=__name__) URL = 'https://release-monitoring.org/api/project/{pkg}' async def get_version(name, conf, **kwargs): pkg = conf.get('anitya') url = URL.format(pkg = pkg) async with session.get(url) as res: data = await res.json() return data['version'] nvchecker-1.4.3/nvchecker/source/archpkg.py000066400000000000000000000014611344563113400207360ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import structlog from . import session, conf_cacheable_with_name logger = structlog.get_logger(logger_name=__name__) URL = 'https://www.archlinux.org/packages/search/json/' get_cacheable_conf = conf_cacheable_with_name('archpkg') async def get_version(name, conf, **kwargs): pkg = conf.get('archpkg') or name strip_release = conf.getboolean('strip-release', False) async with session.get(URL, params={"name": pkg}) as res: data = await res.json() if not data['results']: logger.error('Arch package not found', name=name) return r = [r for r in data['results'] if r['repo'] != 'testing'][0] if strip_release: version = r['pkgver'] else: version = r['pkgver'] + '-' + r['pkgrel'] return version nvchecker-1.4.3/nvchecker/source/aur.py000066400000000000000000000020161344563113400201030ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import structlog from datetime import datetime from . import session, conf_cacheable_with_name logger = structlog.get_logger(logger_name=__name__) AUR_URL = 'https://aur.archlinux.org/rpc/?v=5&type=info&arg[]=' get_cacheable_conf = conf_cacheable_with_name('aur') async def get_version(name, conf, **kwargs): aurname = conf.get('aur') or name use_last_modified = conf.getboolean('use_last_modified', False) strip_release = conf.getboolean('strip-release', False) async with session.get(AUR_URL, params={"v": 5, "type": "info", "arg[]": aurname}) as res: data = await res.json() if not data['results']: logger.error('AUR upstream not found', name=name) return version = data['results'][0]['Version'] if use_last_modified: version += '-' + datetime.utcfromtimestamp(data['results'][0]['LastModified']).strftime('%Y%m%d%H%M%S') if strip_release and '-' in version: version = version.rsplit('-', 1)[0] return version nvchecker-1.4.3/nvchecker/source/bitbucket.py000066400000000000000000000020731344563113400212730ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. from . import session from ..sortversion import sort_version_keys # doc: https://confluence.atlassian.com/display/BITBUCKET/commits+or+commit+Resource BITBUCKET_URL = 'https://bitbucket.org/api/2.0/repositories/%s/commits/%s' BITBUCKET_MAX_TAG = 'https://bitbucket.org/api/1.0/repositories/%s/tags' async def get_version(name, conf, **kwargs): repo = conf.get('bitbucket') br = conf.get('branch', '') use_max_tag = conf.getboolean('use_max_tag', False) ignored_tags = conf.get("ignored_tags", "").split() sort_version_key = sort_version_keys[conf.get("sort_version_key", "parse_version")] if use_max_tag: url = BITBUCKET_MAX_TAG % repo else: url = BITBUCKET_URL % (repo, br) async with session.get(url) as res: data = await res.json() if use_max_tag: data = [tag for tag in data if tag not in ignored_tags] data.sort(key=sort_version_key) version = data[-1] else: version = data['values'][0]['date'].split('T', 1)[0].replace('-', '') return version nvchecker-1.4.3/nvchecker/source/cmd.py000066400000000000000000000015141344563113400200610ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import asyncio import structlog logger = structlog.get_logger(logger_name=__name__) async def get_version(name, conf, **kwargs): cmd = conf['cmd'] p = await asyncio.create_subprocess_shell( cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) output, error = await p.communicate() output = output.strip().decode('latin1') error = error.strip().decode(errors='replace') if p.returncode != 0: logger.error('command exited with error', cmd=cmd, error=error, name=name, returncode=p.returncode) elif not output: logger.error('command exited without output', cmd=cmd, error=error, name=name, returncode=p.returncode) else: return output nvchecker-1.4.3/nvchecker/source/cpan.py000066400000000000000000000005271344563113400202420ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. from .simple_json import simple_json # Using metacpan CPAN_URL = 'https://fastapi.metacpan.org/release/%s' def _version_from_json(data): return str(data['version']) get_version, get_cacheable_conf = simple_json( CPAN_URL, 'cpan', _version_from_json, ) nvchecker-1.4.3/nvchecker/source/cratesio.py000066400000000000000000000007441344563113400211330ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2018 lilydjwg , et al. from . import session, conf_cacheable_with_name API_URL = 'https://crates.io/api/v1/crates/%s' get_cacheable_conf = conf_cacheable_with_name('cratesio') async def get_version(name, conf, **kwargs): name = conf.get('cratesio') or name async with session.get(API_URL % name) as res: data = await res.json() version = [v['num'] for v in data['versions'] if not v['yanked']][0] return version nvchecker-1.4.3/nvchecker/source/debianpkg.py000066400000000000000000000015431344563113400212440ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2017 Felix Yan , et al. import structlog from . import session, conf_cacheable_with_name logger = structlog.get_logger(logger_name=__name__) URL = 'https://sources.debian.org/api/src/%(pkgname)s/?suite=%(suite)s' get_cacheable_conf = conf_cacheable_with_name('debianpkg') async def get_version(name, conf, **kwargs): pkg = conf.get('debianpkg') or name strip_release = conf.getboolean('strip-release', False) suite = conf.get('suite') or "sid" url = URL % {"pkgname": pkg, "suite": suite} async with session.get(url) as res: data = await res.json() if not data.get('versions'): logger.error('Debian package not found', name=name) return r = data['versions'][0] if strip_release: version = r['version'].split("-")[0] else: version = r['version'] return version nvchecker-1.4.3/nvchecker/source/gems.py000066400000000000000000000005101344563113400202440ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. from .simple_json import simple_json GEMS_URL = 'https://rubygems.org/api/v1/versions/%s.json' def _version_from_json(data): return data[0]['number'] get_version, get_cacheable_conf = simple_json( GEMS_URL, 'gems', _version_from_json, ) nvchecker-1.4.3/nvchecker/source/github.py000066400000000000000000000102261344563113400206000ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2018 lilydjwg , et al. import os import re import time from urllib.parse import urlencode from functools import partial import structlog from . import session, HTTPError logger = structlog.get_logger(logger_name=__name__) GITHUB_URL = 'https://api.github.com/repos/%s/commits' GITHUB_LATEST_RELEASE = 'https://api.github.com/repos/%s/releases/latest' # https://developer.github.com/v3/git/refs/#get-all-references GITHUB_MAX_TAG = 'https://api.github.com/repos/%s/git/refs/tags' async def get_version(name, conf, **kwargs): try: return await get_version_real(name, conf, **kwargs) except HTTPError as e: check_ratelimit(e, name) async def get_version_real(name, conf, **kwargs): repo = conf.get('github') br = conf.get('branch') path = conf.get('path') use_latest_release = conf.getboolean('use_latest_release', False) use_max_tag = conf.getboolean('use_max_tag', False) include_tags_pattern = conf.get("include_tags_pattern", "") ignored_tags = conf.get("ignored_tags", "").split() if use_latest_release: url = GITHUB_LATEST_RELEASE % repo elif use_max_tag: url = GITHUB_MAX_TAG % repo else: url = GITHUB_URL % repo parameters = {} if br: parameters['sha'] = br if path: parameters['path'] = path url += '?' + urlencode(parameters) headers = { 'Accept': 'application/vnd.github.quicksilver-preview+json', 'User-Agent': 'lilydjwg/nvchecker', } if 'NVCHECKER_GITHUB_TOKEN' in os.environ: headers['Authorization'] = 'token %s' % os.environ['NVCHECKER_GITHUB_TOKEN'] else: key = kwargs['keyman'].get_key('github') if key: headers['Authorization'] = 'token %s' % key kwargs = {} if conf.get('proxy'): kwargs["proxy"] = conf.get("proxy") if use_max_tag: return await max_tag(partial( session.get, headers=headers, **kwargs), url, name, ignored_tags, include_tags_pattern, max_page = conf.getint("max_page", 1), ) async with session.get(url, headers=headers, **kwargs) as res: logger.debug('X-RateLimit-Remaining', n=res.headers.get('X-RateLimit-Remaining')) data = await res.json() if use_latest_release: if 'tag_name' not in data: logger.error('No tag found in upstream repository.', name=name) return version = data['tag_name'] else: # YYYYMMDD.HHMMSS version = data[0]['commit']['committer']['date'] \ .rstrip('Z').replace('-', '').replace(':', '').replace('T', '.') return version async def max_tag( getter, url, name, ignored_tags, include_tags_pattern, max_page, ): # paging is needed tags = [] for _ in range(max_page): async with getter(url) as res: logger.debug('X-RateLimit-Remaining', n=res.headers.get('X-RateLimit-Remaining')) links = res.headers.get('Link') j = await res.json() data = [] for ref in j: tag = ref['ref'].split('/', 2)[-1] if tag in ignored_tags: continue data.append(tag) if include_tags_pattern: data = [x for x in data if re.search(include_tags_pattern, x)] if data: tags += data next_page_url = get_next_page_url(links) if not next_page_url: break else: url = next_page_url if not tags: logger.error('No tag found in upstream repository.', name=name, include_tags_pattern=include_tags_pattern) return tags def get_next_page_url(links): if not links: return links = links.split(', ') next_link = [x for x in links if x.endswith('rel="next"')] if not next_link: return return next_link[0].split('>', 1)[0][1:] def check_ratelimit(exc, name): res = exc.response if not res: raise # default -1 is used to re-raise the exception n = int(res.headers.get('X-RateLimit-Remaining', -1)) if n == 0: reset = int(res.headers.get('X-RateLimit-Reset')) logger.error('rate limited, resetting at %s. ' 'Or get an API token to increase the allowance if not yet' % time.ctime(reset), name = name, reset = reset) else: raise nvchecker-1.4.3/nvchecker/source/gitlab.py000066400000000000000000000040631344563113400205620ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2018 lilydjwg , et al. import os import urllib.parse import structlog from . import session, HTTPError logger = structlog.get_logger(logger_name=__name__) GITLAB_URL = 'https://%s/api/v4/projects/%s/repository/commits?ref_name=%s' GITLAB_MAX_TAG = 'https://%s/api/v4/projects/%s/repository/tags' async def get_version(name, conf, **kwargs): try: return await get_version_real(name, conf, **kwargs) except HTTPError as e: check_ratelimit(e, name) async def get_version_real(name, conf, **kwargs): repo = urllib.parse.quote_plus(conf.get('gitlab')) br = conf.get('branch', 'master') host = conf.get('host', "gitlab.com") use_max_tag = conf.getboolean('use_max_tag', False) ignored_tags = conf.get("ignored_tags", "").split() if use_max_tag: url = GITLAB_MAX_TAG % (host, repo) else: url = GITLAB_URL % (host, repo, br) # Load token from config token = conf.get('token') # Load token from environ if token is None: env_name = "NVCHECKER_GITLAB_TOKEN_" + host.upper().replace(".", "_").replace("/", "_") token = os.environ.get(env_name) # Load token from keyman if token is None and 'keyman' in kwargs: key_name = 'gitlab_' + host.lower().replace('.', '_').replace("/", "_") token = kwargs['keyman'].get_key(key_name) # Set private token if token exists. headers = {} if token: headers["PRIVATE-TOKEN"] = token async with session.get(url, headers=headers) as res: data = await res.json() if use_max_tag: version = [tag["name"] for tag in data if tag["name"] not in ignored_tags] else: version = data[0]['created_at'].split('T', 1)[0].replace('-', '') return version def check_ratelimit(exc, name): res = exc.response if not res: raise # default -1 is used to re-raise the exception n = int(res.headers.get('RateLimit-Remaining', -1)) if n == 0: logger.error('rate limited, resetting at (unknown). ' 'Or get an API token to increase the allowance if not yet', name = name) else: raise nvchecker-1.4.3/nvchecker/source/hackage.py000066400000000000000000000005421344563113400207010ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. from .simple_json import simple_json HACKAGE_URL = 'https://hackage.haskell.org/package/%s/preferred.json' def _version_from_json(data): return data['normal-version'][0] get_version, get_cacheable_conf = simple_json( HACKAGE_URL, 'hackage', _version_from_json, ) nvchecker-1.4.3/nvchecker/source/manual.py000066400000000000000000000002511344563113400205700ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. async def get_version(name, conf, **kwargs): return conf.get('manual').strip() or None nvchecker-1.4.3/nvchecker/source/npm.py000066400000000000000000000005001344563113400201020ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. from .simple_json import simple_json NPM_URL = 'https://registry.npmjs.org/%s' def _version_from_json(data): return data['dist-tags']['latest'] get_version, get_cacheable_conf = simple_json( NPM_URL, 'npm', _version_from_json, ) nvchecker-1.4.3/nvchecker/source/packagist.py000066400000000000000000000007721344563113400212710ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. from .simple_json import simple_json PACKAGIST_URL = 'https://packagist.org/packages/%s.json' def _version_from_json(data): data = {version: details for version, details in data["package"]['versions'].items() if version != "dev-master"} if len(data): return max(data, key=lambda version: data[version]["time"]) get_version, get_cacheable_conf = simple_json( PACKAGIST_URL, 'packagist', _version_from_json, ) nvchecker-1.4.3/nvchecker/source/pacman.py000066400000000000000000000010671344563113400205600ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. from . import cmd, conf_cacheable_with_name get_cacheable_conf = conf_cacheable_with_name('debianpkg') async def get_version(name, conf, **kwargs): referree = conf.get('pacman') or name c = "LANG=C pacman -Si %s | grep -F Version | awk '{print $3}'" % referree conf['cmd'] = c strip_release = conf.getboolean('strip-release', False) version = await cmd.get_version(name, conf) if strip_release and '-' in version: version = version.rsplit('-', 1)[0] return version nvchecker-1.4.3/nvchecker/source/pypi.py000066400000000000000000000011141344563113400202730ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. from . import conf_cacheable_with_name, session get_cacheable_conf = conf_cacheable_with_name('pypi') async def get_version(name, conf, **kwargs): package = conf.get('pypi') or name use_pre_release = conf.getboolean('use_pre_release', False) url = 'https://pypi.org/pypi/{}/json'.format(package) async with session.get(url) as res: data = await res.json() if use_pre_release: version = sorted(data['releases'].keys())[-1] else: version = data['info']['version'] return version nvchecker-1.4.3/nvchecker/source/regex.py000066400000000000000000000017011344563113400204260ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import re import sre_constants import structlog from . import session logger = structlog.get_logger(logger_name=__name__) async def get_version(name, conf, **kwargs): try: regex = re.compile(conf['regex']) except sre_constants.error: logger.warning('bad regex, skipped.', name=name, exc_info=True) return encoding = conf.get('encoding', 'latin1') kwargs = {} headers = {} if conf.get('proxy'): kwargs["proxy"] = conf.get("proxy") if conf.get('user_agent'): headers['User-Agent'] = conf['user_agent'] async with session.get(conf['url'], headers=headers, **kwargs) as res: body = (await res.read()).decode(encoding) try: version = regex.findall(body) except ValueError: version = None if not conf.getboolean('missing_ok', False): logger.error('version string not found.', name=name) return version nvchecker-1.4.3/nvchecker/source/simple_json.py000066400000000000000000000011661344563113400216430ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. from . import session, conf_cacheable_with_name def simple_json(urlpat, confkey, version_from_json): async def get_version(name, conf, **kwargs): repo = conf.get(confkey) or name url = urlpat % repo kwargs = {} if conf.get('proxy'): kwargs["proxy"] = conf.get('proxy') async with session.get(url, **kwargs) as res: data = await res.json(content_type=None) version = version_from_json(data) return version get_cacheable_conf = conf_cacheable_with_name(confkey) return get_version, get_cacheable_conf nvchecker-1.4.3/nvchecker/source/tornado_httpclient.py000066400000000000000000000035701344563113400232260ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import json from urllib.parse import urlencode from tornado.httpclient import AsyncHTTPClient, HTTPRequest, HTTPResponse from tornado.httpclient import HTTPError from tornado.platform.asyncio import AsyncIOMainLoop, to_asyncio_future AsyncIOMainLoop().install() try: import pycurl AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient", max_clients=20) except ImportError: pycurl = None __all__ = ['session', 'HTTPError'] client = AsyncHTTPClient() HTTP2_AVAILABLE = None if pycurl else False def try_use_http2(curl): global HTTP2_AVAILABLE if HTTP2_AVAILABLE is None: try: curl.setopt(pycurl.HTTP_VERSION, 4) HTTP2_AVAILABLE = True except pycurl.error: HTTP2_AVAILABLE = False elif HTTP2_AVAILABLE: curl.setopt(pycurl.HTTP_VERSION, 4) class Session: def get(self, url, **kwargs): kwargs['prepare_curl_callback'] = try_use_http2 proxy = kwargs.get('proxy') if proxy: del kwargs['proxy'] elif hasattr(self, 'nv_config') and self.nv_config.get('proxy'): proxy = self.nv_config.get('proxy') if proxy: host, port = proxy.rsplit(':', 1) kwargs['proxy_host'] = host kwargs['proxy_port'] = int(port) params = kwargs.get('params') if params: del kwargs['params'] q = urlencode(params) url += '?' + q r = HTTPRequest(url, **kwargs) return ResponseManager(r) class ResponseManager: def __init__(self, req): self.req = req async def __aenter__(self): return await to_asyncio_future(client.fetch(self.req)) async def __aexit__(self, exc_type, exc, tb): pass async def json_response(self, **kwargs): return json.loads(self.body.decode('utf-8')) async def read(self): return self.body HTTPResponse.json = json_response HTTPResponse.read = read session = Session() nvchecker-1.4.3/nvchecker/source/ubuntupkg.py000066400000000000000000000025331344563113400213440ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2017 Felix Yan , et al. import structlog from . import session, conf_cacheable_with_name logger = structlog.get_logger(logger_name=__name__) URL = 'https://api.launchpad.net/1.0/ubuntu/+archive/primary?ws.op=getPublishedSources&source_name=%s&exact_match=true' get_cacheable_conf = conf_cacheable_with_name('ubuntupkg') async def get_version(name, conf, **kwargs): pkg = conf.get('ubuntupkg') or name strip_release = conf.getboolean('strip-release', False) suite = conf.get('suite') url = URL % pkg if suite: suite = "https://api.launchpad.net/1.0/ubuntu/" + suite releases = [] while not releases: async with session.get(url) as res: data = await res.json() if not data.get('entries'): logger.error('Ubuntu package not found', name=name) return releases = [r for r in data["entries"] if r["status"] == "Published"] if suite: releases = [r for r in releases if r["distro_series_link"] == suite] if "next_collection_link" not in data: break url = data["next_collection_link"] if not releases: logger.error('Ubuntu package not found', name=name) return if strip_release: version = releases[0]['source_package_version'].split("-")[0] else: version = releases[0]['source_package_version'] return version nvchecker-1.4.3/nvchecker/source/vcs.py000066400000000000000000000035131344563113400201120ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2018 lilydjwg , et al. import asyncio import os.path as _path from pkg_resources import parse_version import structlog from . import conf_cacheable_with_name logger = structlog.get_logger(logger_name=__name__) _self_path = _path.dirname(_path.abspath(__file__)) _cmd_prefix = ['/bin/bash', _path.join(_self_path, 'vcs.sh')] PROT_VER = 1 get_cacheable_conf = conf_cacheable_with_name('vcs') def _parse_oldver(oldver): if oldver is None: return PROT_VER, 0, '' try: prot_ver, count, ver = oldver.split('.', maxsplit=2) prot_ver = int(prot_ver) count = int(count) except: return PROT_VER, 0, '' if prot_ver != PROT_VER: return PROT_VER, 0, ver return PROT_VER, count, ver async def get_version(name, conf, **kwargs): vcs = conf['vcs'] or '' use_max_tag = conf.getboolean('use_max_tag', False) ignored_tags = conf.get("ignored_tags", "").split() oldver = conf.get('oldver') cmd = _cmd_prefix + [name, vcs] if use_max_tag: cmd += ["get_tags"] p = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) output, error = await p.communicate() output = output.strip().decode('latin1') error = error.strip().decode('latin1') if p.returncode != 0: logger.error('command exited with error', output=output, name=name, returncode=p.returncode, error=error) return else: if use_max_tag: data = [tag for tag in output.split("\n") if tag not in ignored_tags] data.sort(key=parse_version) version = data[-1] return version else: oldvers = _parse_oldver(oldver) if output == oldvers[2]: return oldver else: return "%d.%d.%s" % (oldvers[0], oldvers[1] + 1, output) nvchecker-1.4.3/nvchecker/source/vcs.sh000066400000000000000000000053501344563113400200750ustar00rootroot00000000000000#!/bin/bash exec 3>&1 exec >&2 dir=$1 vcs=$2 get_tags=$3 parse_vcs_url() { local _url=$1 local _out_var=$2 # remove folder:: [[ $_url =~ ^[^/:]*::(.*)$ ]] && _url=${BASH_REMATCH[1]} [[ $_url =~ ^(bzr|git|hg|svn)([+:])(.*) ]] || return 1 local _proto=${BASH_REMATCH[1]} [[ ${BASH_REMATCH[2]} = + ]] && _url=${BASH_REMATCH[3]} local _real_url=${_url%\#*} local _frag='' [[ $_real_url = $_url ]] || _frag=${_url##*\#} eval "${_out_var}"'=("${_proto}" "${_real_url}" "${_frag}")' } get_vcs() { local _vcs=$1 local _out_var=$2 if [[ -z $_vcs ]]; then _vcs=$(. "${dir}"/PKGBUILD &> /dev/null for src in "${source[@]}"; do parse_vcs_url "$src" _ && { echo "$src" exit 0 } done exit 1) || return 1 fi parse_vcs_url "$_vcs" "$_out_var" } git_get_version() { local _url=$1 local _frag=$2 local _ref='' if [[ -z $_frag ]]; then _ref=HEAD elif [[ $_frag =~ ^commit=(.*)$ ]]; then echo "${BASH_REMATCH[1]}" return 0 elif [[ $_frag =~ ^branch=(.*)$ ]]; then _ref=refs/heads/${BASH_REMATCH[1]} elif [[ $_frag =~ ^tag=(.*)$ ]]; then _ref=refs/tags/${BASH_REMATCH[1]} else return 1 fi local _res=$(git ls-remote "$_url" "$_ref") [[ $_res =~ ^([a-fA-F0-9]*)[[:blank:]] ]] || return 1 echo "${BASH_REMATCH[1]}" } hg_get_version() { local _url=$1 local _frag=$2 local _ref if [[ -z $_frag ]]; then _ref=default elif [[ $_frag =~ ^(revision|tag|branch)=(.*)$ ]]; then _ref=${BASH_REMATCH[2]} else return 1 fi hg identify "${_url}#${_ref}" } svn_get_version() { local _url=$1 local _frag=$2 local _extra_arg=() if [[ -z $_frag ]]; then true elif [[ $_frag =~ ^(revision)=(.*)$ ]]; then _extra_arg=(-r "${BASH_REMATCH[2]}") else return 1 fi # Get rid of locale env -i PATH="${PATH}" svn info "${_extra_arg[@]}" "${_url}" | \ sed -n 's/^Revision:[[:blank:]]*\([0-9]*\)/\1/p' } bzr_get_version() { local _url=$1 local _frag=$2 local _extra_arg=() if [[ -z $_frag ]]; then true elif [[ $_frag =~ ^(revision)=(.*)$ ]]; then _extra_arg=(-r "${BASH_REMATCH[2]}") else return 1 fi bzr revno -q "${_extra_arg[@]}" "${_url}" } git_get_tags() { local _url=$1 git ls-remote "$_url" | grep -oP '(?<=refs/tags/)[^^]*$' } get_vcs "${vcs}" components || exit 1 if [[ "x$get_tags" == "xget_tags" ]]; then eval "${components[0]}_get_tags"' ${components[@]:1}' >&3 else eval "${components[0]}_get_version"' ${components[@]:1}' >&3 fi nvchecker-1.4.3/nvchecker/tools.py000066400000000000000000000040071344563113400171560ustar00rootroot00000000000000# vim: se sw=2: # MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import sys import os import argparse import structlog from . import core logger = structlog.get_logger(logger_name=__name__) def take(): parser = argparse.ArgumentParser(description='update version records of nvchecker') core.add_common_arguments(parser) parser.add_argument('--all', action='store_true', help='take all updates') parser.add_argument('--ignore-nonexistent', action='store_true', help='ignore nonexistent names') parser.add_argument('names', metavar='NAME', nargs='*', help='software name to be updated') args = parser.parse_args() if core.process_common_arguments(args): return s = core.Source(args.file) if not s.oldver or not s.newver: logger.critical( "doesn't have both 'oldver' and 'newver' set.", source=s, ) sys.exit(2) oldvers = core.read_verfile(s.oldver) newvers = core.read_verfile(s.newver) if args.all: oldvers.update(newvers) else: for name in args.names: try: oldvers[name] = newvers[name] except KeyError: if args.ignore_nonexistent: logger.warning('nonexistent in newver, ignored', name=name) continue logger.critical( "doesn't exist in 'newver' set.", name=name, ) sys.exit(2) try: os.rename(s.oldver, s.oldver + '~') except FileNotFoundError: pass core.write_verfile(s.oldver, oldvers) def cmp(): parser = argparse.ArgumentParser(description='compare version records of nvchecker') core.add_common_arguments(parser) args = parser.parse_args() if core.process_common_arguments(args): return s = core.Source(args.file) oldvers = core.read_verfile(s.oldver) if s.oldver else {} newvers = core.read_verfile(s.newver) for name, newver in sorted(newvers.items()): oldver = oldvers.get(name, None) if oldver != newver: print('%s %s -> %s' % (name, oldver, newver)) nvchecker-1.4.3/sample_source.ini000066400000000000000000000011511344563113400170330ustar00rootroot00000000000000[__config__] oldver = old_ver.txt newver = new_ver.txt [vim] url = http://ftp.vim.org/pub/vim/patches/7.3/ regex = 7\.3\.\d+ ; [badone] ; url = http://www.baidu.com/ ; regex = baidu (\d+) [google-chrome] cmd = wget -qO- http://dl.google.com/linux/chrome/rpm/stable/x86_64/repodata/other.xml.gz | zgrep "google-chrome-stable" | awk -F\" '{print $10"-"$12}' [fbcat] aur [winterpy] github = lilydjwg/winterpy [nvchecker] github = lilydjwg/nvchecker [ssed] url = http://sed.sourceforge.net/grabbag/ssed/ regex = The current version is ([\d.]+)\. proxy = localhost:8087 [PySide] pypi = PySide [test] manual = 0.1 nvchecker-1.4.3/scripts/000077500000000000000000000000001344563113400151625ustar00rootroot00000000000000nvchecker-1.4.3/scripts/README.rst000066400000000000000000000000451344563113400166500ustar00rootroot00000000000000Additional scripts may help someone. nvchecker-1.4.3/scripts/arch_repopkg_unmonitored000077500000000000000000000014101344563113400221730ustar00rootroot00000000000000#!/bin/bash -e # MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. if [[ $# -ne 2 || ! -r $1 || -z $2 ]]; then echo >&2 "usage: arch_repopkg_unmonitored config.ini pkg_list or repository_name" exit 1 fi tmp_configured=$(mktemp) tmp_list=$(mktemp) trap 'rm -f "$tmp_configured" "$tmp_list"' INT QUIT TERM EXIT config=$1 if [[ -r $2 ]]; then sort "$2" > "$tmp_list" else pacman -Sl "$2" | cut -d' ' -f2 | sort > "$tmp_list" fi # Also take commented-out items into account grep -oP '^(?:[;#]\s*)?\[.*(?=]$)' "$config" | sed 's/^\([;#]\s*\)\?\[//;/^__/d' | sort > "$tmp_configured" echo ">>> Un-monitored list:" comm -23 "$tmp_list" "$tmp_configured" echo echo ">>> Monitored but not in list:" comm -13 "$tmp_list" "$tmp_configured" echo nvchecker-1.4.3/scripts/gen_contents000077500000000000000000000013731344563113400176020ustar00rootroot00000000000000#!/usr/bin/env python3 # MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import sys import re sections = re.compile(r'[-=]{3,}') levels = { '=': 1, '-': 2, } def to_id(t): return re.sub(r'[`(),]', '', t).lower().replace(' ', '-') def to_text(t): return t.replace('`', '') def main(f): L = [] last = None for l in f: l = l[:-1] if sections.fullmatch(l): L.append((last, levels[l[0]])) last = l last_lvl = 1 for title, lvl in L: indent = ' ' * (lvl - 1) if last_lvl != lvl: print() print('{indent}* `{text} <#{id}>`_'.format( indent = indent, text = to_text(title), id = to_id(title), )) last_lvl = lvl if __name__ == '__main__': main(sys.stdin) nvchecker-1.4.3/scripts/maintained_in_aur000077500000000000000000000012211344563113400205520ustar00rootroot00000000000000#!/usr/bin/env python3 # MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. '''show a list of packages maintained by someone in AUR''' import sys import json import urllib.request def main(user): url = 'https://aur.archlinux.org/rpc.php?type=msearch&arg=' + user res = urllib.request.urlopen(url) if res.status != 200: sys.exit('Error: %d %s' % (res.status, res.reason)) d = res.read().decode('utf-8') d = json.loads(d) print('\n'.join(sorted(pkg['Name'] for pkg in d['results']))) if __name__ == '__main__': main(sys.argv[1]) if len(sys.argv) == 2 else \ sys.exit('whose packages do you want to see?') nvchecker-1.4.3/setup.cfg000066400000000000000000000001061344563113400153110ustar00rootroot00000000000000[flake8] ignore = E111, E302, E501 [tool:pytest] # addopts = -n auto nvchecker-1.4.3/setup.py000077500000000000000000000035631344563113400152170ustar00rootroot00000000000000#!/usr/bin/env python3 from setuptools import setup, find_packages import nvchecker # The complex upload command: # rm -rf dist && python setup.py sdist && twine check dist/* && twine upload -s dist/* setup( name = 'nvchecker', version = nvchecker.__version__, author = 'lilydjwg', author_email = 'lilydjwg@gmail.com', description = 'New version checker for software', license = 'MIT', keywords = 'new version build check', url = 'https://github.com/lilydjwg/nvchecker', long_description = open('README.rst', encoding='utf-8').read(), long_description_content_type = 'text/x-rst', platforms = 'any', zip_safe = False, packages = find_packages(exclude=["tests"]), install_requires = ['setuptools', 'structlog', 'tornado', 'pycurl'], extras_require = { 'vercmp': ['pyalpm'], }, tests_require = [ 'pytest', 'pytest-asyncio', 'pytest-xdist', 'flaky', ], entry_points = { 'console_scripts': [ 'nvchecker = nvchecker.main:main', 'nvtake = nvchecker.tools:take', 'nvcmp = nvchecker.tools:cmp', ], }, package_data = {'nvchecker': ['source/vcs.sh']}, classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development", "Topic :: System :: Archiving :: Packaging", "Topic :: System :: Software Distribution", "Topic :: Utilities", ], ) nvchecker-1.4.3/tests/000077500000000000000000000000001344563113400146355ustar00rootroot00000000000000nvchecker-1.4.3/tests/__init__.py000066400000000000000000000000001344563113400167340ustar00rootroot00000000000000nvchecker-1.4.3/tests/conftest.py000066400000000000000000000034511344563113400170370ustar00rootroot00000000000000import configparser import pytest import asyncio import io import structlog from nvchecker.get_version import get_version as _get_version from nvchecker.get_version import _cache from nvchecker.core import Source class TestSource(Source): def __init__(self, future, *args, **kwargs): super().__init__(*args, **kwargs) self._future = future def on_update(self, name, version, oldver): self._future.set_result(version) def on_no_result(self, name): self._future.set_result(None) def on_exception(self, name, exc): self._future.set_exception(exc) @pytest.fixture(scope="module") async def run_source(): async def __call__(conf, *, clear_cache=False): if clear_cache: _cache.clear() future = asyncio.Future() file = io.StringIO(conf) file.name = '' s = TestSource(future, file) await s.check() return await future return __call__ @pytest.fixture(scope="module") async def get_version(): async def __call__(name, config): if isinstance(config, dict): _config = configparser.ConfigParser(dict_type=dict, allow_no_value=True) _config.read_dict({name: config}) config = _config[name] return await _get_version(name, config) return __call__ @pytest.fixture(scope="module") def event_loop(request): """Override pytest-asyncio's event_loop fixture, Don't create an instance of the default event loop for each test case. """ loop = asyncio.get_event_loop() yield loop @pytest.fixture(scope="session", autouse=True) def raise_on_logger_msg(): def proc(logger, method_name, event_dict): if method_name in ('warning', 'error'): if 'exc_info' in event_dict: raise event_dict['exc_info'] raise RuntimeError(event_dict['event']) return event_dict['event'] structlog.configure([proc]) nvchecker-1.4.3/tests/test_android.py000066400000000000000000000007361344563113400176740ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2017 Yen Chi Hsuan import pytest pytestmark = pytest.mark.asyncio async def test_android_addon(get_version): assert await get_version("android-google-play-apk-expansion", {"android_sdk": "extras;google;market_apk_expansion", "repo": "addon"}) == "1.r03" async def test_android_package(get_version): assert await get_version("android-sdk-cmake", {"android_sdk": "cmake;", "repo": "package"}) == "3.6.4111459" nvchecker-1.4.3/tests/test_anitya.py000066400000000000000000000003671344563113400175410ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2017 Felix Yan , et al. import pytest pytestmark = pytest.mark.asyncio async def test_anitya(get_version): assert await get_version("shutter", {"anitya": "fedora/shutter"}) == "0.94.2" nvchecker-1.4.3/tests/test_archpkg.py000066400000000000000000000006431344563113400176700ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. from flaky import flaky import pytest pytestmark = pytest.mark.asyncio @flaky async def test_archpkg(get_version): assert await get_version("ipw2100-fw", {"archpkg": None}) == "1.3-9" @flaky async def test_archpkg_strip_release(get_version): assert await get_version("ipw2100-fw", {"archpkg": None, "strip-release": 1}) == "1.3" nvchecker-1.4.3/tests/test_aur.py000066400000000000000000000011251344563113400170340ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. from flaky import flaky import pytest pytestmark = pytest.mark.asyncio @flaky(max_runs=10) async def test_aur(get_version): assert await get_version("ssed", {"aur": None}) == "3.62-2" @flaky(max_runs=10) async def test_aur_strip_release(get_version): assert await get_version("ssed", {"aur": None, "strip-release": 1}) == "3.62" @flaky(max_runs=10) async def test_aur_use_last_modified(get_version): assert await get_version("ssed", {"aur": None, 'use_last_modified': True}) == "3.62-2-20150725052412" nvchecker-1.4.3/tests/test_bitbucket.py000066400000000000000000000011321344563113400202170ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import pytest pytestmark = pytest.mark.asyncio async def test_bitbucket(get_version): assert await get_version("example", {"bitbucket": "prawee/git-tag"}) == "20150303" async def test_bitbucket_max_tag(get_version): assert await get_version("example", {"bitbucket": "prawee/git-tag", "use_max_tag": 1}) == "1.7.0" async def test_bitbucket_max_tag_with_ignored_tags(get_version): assert await get_version("example", {"bitbucket": "prawee/git-tag", "use_max_tag": 1, "ignored_tags": "1.6.0 1.7.0"}) == "v1.5" nvchecker-1.4.3/tests/test_cache.py000066400000000000000000000005421344563113400173120ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2018 lilydjwg , et al. import pytest pytestmark = pytest.mark.asyncio async def test_cache(get_version): a = await get_version("a", {"cmd": "date +%%Y-%%m-%%d"}) b = await get_version("b", {"cmd": "date +%%Y-%%m-%%d"}) c = await get_version("c", {"cmd": "date"}) assert a == b assert a != c nvchecker-1.4.3/tests/test_cmd.py000066400000000000000000000005611344563113400170130ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import pytest pytestmark = pytest.mark.asyncio async def test_cmd(get_version): assert await get_version("example", {"cmd": "echo Meow"}) == "Meow" async def test_cmd_complex(get_version): assert await get_version("example", {"cmd": "echo Meow | sed 's/meow/woof/i'"}) == "woof" nvchecker-1.4.3/tests/test_cpan.py000066400000000000000000000003751344563113400171740ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import pytest pytestmark = pytest.mark.asyncio async def test_cpan(get_version): assert await get_version("POE-Component-Server-HTTPServer", {"cpan": None}) == "0.9.2" nvchecker-1.4.3/tests/test_cratesio.py000066400000000000000000000003551344563113400200620ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import pytest pytestmark = pytest.mark.asyncio async def test_cratesio(get_version): assert await get_version("example", {"cratesio": None}) == "0.1.0" nvchecker-1.4.3/tests/test_debianpkg.py000066400000000000000000000012251344563113400201720ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2017 Felix Yan , et al. from flaky import flaky import pytest pytestmark = pytest.mark.asyncio @flaky(max_runs=10) async def test_debianpkg(get_version): assert await get_version("sigrok-firmware-fx2lafw", {"debianpkg": None}) == "0.1.6-1" @flaky(max_runs=10) async def test_debianpkg_strip_release(get_version): assert await get_version("sigrok-firmware-fx2lafw", {"debianpkg": None, "strip-release": 1}) == "0.1.6" @flaky(max_runs=10) async def test_debianpkg_suite(get_version): assert await get_version("sigrok-firmware-fx2lafw", {"debianpkg": None, "suite": "jessie"}) == "0.1.2-1" nvchecker-1.4.3/tests/test_gems.py000066400000000000000000000003451344563113400172030ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import pytest pytestmark = pytest.mark.asyncio async def test_gems(get_version): assert await get_version("example", {"gems": None}) == "1.0.2" nvchecker-1.4.3/tests/test_github.py000066400000000000000000000044441344563113400175360ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2018 lilydjwg , et al. import os import re import pytest pytestmark = [pytest.mark.asyncio, pytest.mark.skipif("NVCHECKER_GITHUB_TOKEN" not in os.environ, reason="requires NVCHECKER_GITHUB_TOKEN, or it fails too much")] async def test_github(get_version): assert await get_version("example", {"github": "harry-sanabria/ReleaseTestRepo"}) == "20140122.012101" async def test_github_default_not_master(get_version): assert await get_version("example", {"github": "MariaDB/server"}) is not None async def test_github_latest_release(get_version): assert await get_version("example", {"github": "harry-sanabria/ReleaseTestRepo", "use_latest_release": 1}) == "release3" async def test_github_max_tag(get_version): assert await get_version("example", {"github": "harry-sanabria/ReleaseTestRepo", "use_max_tag": 1}) == "second_release" async def test_github_max_tag_with_ignored_tags(get_version): assert await get_version("example", {"github": "harry-sanabria/ReleaseTestRepo", "use_max_tag": 1, "ignored_tags": "second_release release3"}) == "first_release" async def test_github_max_tag_with_ignored(get_version): assert await get_version("example", {"github": "harry-sanabria/ReleaseTestRepo", "use_max_tag": 1, "ignored": "second_release release3"}) == "first_release" async def test_github_with_path(get_version): assert await get_version("example", {"github": "petronny/ReleaseTestRepo", "path": "test_directory"}) == "20140122.012101" async def test_github_with_path_and_branch(get_version): assert await get_version("example", {"github": "petronny/ReleaseTestRepo", "branch": "test", "path": "test_directory/test_directory"}) == "20190128.113201" async def test_github_max_tag_with_include_old(get_version): version = await get_version("example", { "github": "EFForg/https-everywhere", "use_max_tag": 1, "include_tags_pattern": r"^chrome-\d", }) assert re.match(r'chrome-[\d.]+', version) async def test_github_max_tag_with_include(get_version): version = await get_version("example", { "github": "EFForg/https-everywhere", "use_max_tag": 1, "include_regex": r"chrome-\d.*", }) assert re.match(r'chrome-[\d.]+', version) nvchecker-1.4.3/tests/test_gitlab.py000066400000000000000000000024051344563113400175110ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import os import pytest pytestmark = [pytest.mark.asyncio, pytest.mark.skipif("NVCHECKER_GITLAB_TOKEN_GITLAB_COM" not in os.environ, reason="requires NVCHECKER_GITLAB_TOKEN_GITLAB_COM")] async def test_gitlab(get_version): ver = await get_version("example", {"gitlab": "gitlab-org/gitlab-test"}) assert len(ver) == 8 assert ver.isdigit() async def test_gitlab_max_tag(get_version): assert await get_version("example", {"gitlab": "gitlab-org/gitlab-test", "use_max_tag": 1}) == "v1.1.0" async def test_gitlab_max_tag_with_ignored_tags(get_version): assert await get_version("example", {"gitlab": "gitlab-org/gitlab-test", "use_max_tag": 1, "ignored_tags": "v1.1.0"}) == "v1.0.0" async def test_gitlab_max_tag_with_include(get_version): assert await get_version("example", { "gitlab": "gitlab-org/gitlab-test", "use_max_tag": 1, "include_regex": r'v1\.0.*', }) == "v1.0.0" async def test_gitlab_max_tag_with_ignored(get_version): assert await get_version("example", { "gitlab": "gitlab-org/gitlab-test", "use_max_tag": 1, "ignored": "v1.1.0", }) == "v1.0.0" nvchecker-1.4.3/tests/test_gitlab_local.py000066400000000000000000000024421344563113400206640ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import os import pytest import contextlib pytestmark = [pytest.mark.asyncio, pytest.mark.skipif(os.environ.get('TRAVIS') == 'true', reason="rate-limited per IP")] @contextlib.contextmanager def unset_gitlab_token_env(): token = os.environ.get('NVCHECKER_GITLAB_TOKEN_GITLAB_COM') try: if token: del os.environ['NVCHECKER_GITLAB_TOKEN_GITLAB_COM'] yield token finally: if token: os.environ['NVCHECKER_GITLAB_TOKEN_GITLAB_COM'] = token async def test_gitlab(get_version): with unset_gitlab_token_env(): ver = await get_version("example", {"gitlab": "gitlab-org/gitlab-test"}) assert len(ver) == 8 assert ver.isdigit() async def test_gitlab_max_tag(get_version): with unset_gitlab_token_env(): assert await get_version("example", {"gitlab": "gitlab-org/gitlab-test", "use_max_tag": 1}) == "v1.1.0" async def test_gitlab_max_tag_with_ignored_tags(get_version): with unset_gitlab_token_env(): ver = await get_version("example", {"gitlab": "gitlab-org/gitlab-test", "use_max_tag": 1, "ignored_tags": "v1.1.0"}) assert ver == "v1.0.0" nvchecker-1.4.3/tests/test_hackage.py000066400000000000000000000003601344563113400176300ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import pytest pytestmark = pytest.mark.asyncio async def test_hackage(get_version): assert await get_version("sessions", {"hackage": None}) == "2008.7.18" nvchecker-1.4.3/tests/test_keyfile.py000066400000000000000000000033371344563113400177040ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2018 lilydjwg , et al. import os import tempfile import contextlib from nvchecker.source import HTTPError import pytest pytestmark = [pytest.mark.asyncio] @contextlib.contextmanager def unset_github_token_env(): token = os.environ.get('NVCHECKER_GITHUB_TOKEN') try: if token: del os.environ['NVCHECKER_GITHUB_TOKEN'] yield token finally: if token: os.environ['NVCHECKER_GITHUB_TOKEN'] = token async def test_keyfile_missing(run_source): test_conf = '''\ [example] github = harry-sanabria/ReleaseTestRepo ''' assert await run_source(test_conf) in ['20140122.012101', None] async def test_keyfile_invalid(run_source): with tempfile.NamedTemporaryFile(mode='w') as f, \ unset_github_token_env(): f.write('''\ [keys] github = xxx ''') f.flush() test_conf = '''\ [example] github = harry-sanabria/ReleaseTestRepo [__config__] keyfile = {name} '''.format(name=f.name) try: version = await run_source(test_conf, clear_cache=True) assert version is None # out of allowance return except HTTPError as e: assert e.code == 401 return raise Exception('expected 401 response') @pytest.mark.skipif('NVCHECKER_GITHUB_TOKEN' not in os.environ, reason='no key given') async def test_keyfile_valid(run_source): with tempfile.NamedTemporaryFile(mode='w') as f, \ unset_github_token_env() as token: f.write('''\ [keys] github = {token} '''.format(token=token)) f.flush() test_conf = '''\ [example] github = harry-sanabria/ReleaseTestRepo [__config__] keyfile = {name} '''.format(name=f.name) assert await run_source(test_conf) == '20140122.012101' nvchecker-1.4.3/tests/test_manual.py000066400000000000000000000003521344563113400175230ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import pytest pytestmark = pytest.mark.asyncio async def test_manual(get_version): assert await get_version("example", {"manual": "Meow"}) == "Meow" nvchecker-1.4.3/tests/test_npm.py000066400000000000000000000003431344563113400170400ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import pytest pytestmark = pytest.mark.asyncio async def test_npm(get_version): assert await get_version("example", {"npm": None}) == "0.0.0" nvchecker-1.4.3/tests/test_packagist.py000066400000000000000000000004111344563113400202100ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import pytest pytestmark = pytest.mark.asyncio async def test_packagist(get_version): assert await get_version("butterfly/example-web-application", {"packagist": None}) == "1.2.0" nvchecker-1.4.3/tests/test_pacman.py000066400000000000000000000010161344563113400175030ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import shutil import pytest pytestmark = [pytest.mark.asyncio, pytest.mark.skipif(shutil.which("pacman") is None, reason="requires pacman command")] async def test_pacman(get_version): assert await get_version("ipw2100-fw", {"pacman": None}) == "1.3-9" async def test_pacman_strip_release(get_version): assert await get_version("ipw2100-fw", {"pacman": None, "strip-release": 1}) == "1.3" nvchecker-1.4.3/tests/test_proxy.py000066400000000000000000000020061344563113400174250ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2017 Felix Yan , et al. import sys try: import aiohttp except ImportError: aiohttp = None import pytest pytestmark = [ pytest.mark.asyncio, pytest.mark.skipif('nvchecker.source.aiohttp_httpclient' not in sys.modules, reason='aiohttp no chosen'), ] async def test_proxy(get_version, monkeypatch): from nvchecker.source import session async def fake_request(*args, proxy, **kwargs): class fake_response(): status = 200 async def read(): return proxy.encode("ascii") def release(): pass return fake_response monkeypatch.setattr(session, "nv_config", {"proxy": "255.255.255.255:65535"}, raising=False) monkeypatch.setattr(aiohttp.ClientSession, "_request", fake_request) assert await get_version("example", {"regex": "(.+)", "url": "deadbeef"}) == "255.255.255.255:65535" assert await get_version("example", {"regex": "(.+)", "url": "deadbeef", "proxy": "0.0.0.0:0"}) == "0.0.0.0:0" nvchecker-1.4.3/tests/test_pypi.py000066400000000000000000000010331344563113400172240ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import pytest pytestmark = pytest.mark.asyncio async def test_pypi(get_version): assert await get_version("example", {"pypi": None}) == "0.1.0" async def test_pypi_release(get_version): assert await get_version("example-test-package", {"pypi": "example-test-package"}) == "1.0.0" async def test_pypi_pre_release(get_version): assert await get_version("example-test-package", {"pypi": "example-test-package", "use_pre_release": 1}) == "1.0.1a1" nvchecker-1.4.3/tests/test_regex.py000066400000000000000000000014321344563113400173600ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import pytest pytestmark = pytest.mark.asyncio @pytest.mark.skipif(True, reason='httpbin is overloaded?') async def test_regex_httpbin(get_version): assert await get_version("example", { "url": "https://httpbin.org/get", "regex": '"User-Agent": "(\w+)"', "user_agent": "Meow", }) == "Meow" async def test_regex(get_version): assert await get_version("example", { "url": "http://example.net/", "regex": 'for (\w+) examples', }) == "illustrative" async def test_missing_ok(get_version): assert await get_version("example", { "url": "http://example.net/", "regex": "foobar", "missing_ok": True, }) is None nvchecker-1.4.3/tests/test_substitute.py000066400000000000000000000017001344563113400204570ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import pytest pytestmark = pytest.mark.asyncio async def test_substitute_prefix(get_version): assert await get_version("example", {"manual": "v1.0", "prefix": "v"}) == "1.0" async def test_substitute_prefix_missing_ok(get_version): assert await get_version("example", {"manual": "1.0", "prefix": "v"}) == "1.0" async def test_substitute_regex(get_version): assert await get_version("example", {"manual": "r15c", "from_pattern": r"r(\d+)([a-z])", "to_pattern": r"r\1.\2"}) == "r15.c" async def test_substitute_regex_missing_ok(get_version): assert await get_version("example", {"manual": "r15", "from_pattern": r"r(\d+)([a-z])", "to_pattern": r"r\1.\2"}) == "r15" async def test_substitute_prefix_has_higher_priority(get_version): assert await get_version("example", {"manual": "r15", "prefix": "r", "from_pattern": "r(\d+)", "to_pattern": "R\1"}) == "15" nvchecker-1.4.3/tests/test_ubuntupkg.py000066400000000000000000000014351344563113400202750ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2017 Felix Yan , et al. from flaky import flaky import pytest pytestmark = pytest.mark.asyncio @flaky async def test_ubuntupkg(get_version): assert await get_version("sigrok-firmware-fx2lafw", {"ubuntupkg": None}) == "0.1.6-1" @flaky async def test_ubuntupkg_strip_release(get_version): assert await get_version("sigrok-firmware-fx2lafw", {"ubuntupkg": None, "strip-release": 1}) == "0.1.6" @flaky async def test_ubuntupkg_suite(get_version): assert await get_version("sigrok-firmware-fx2lafw", {"ubuntupkg": None, "suite": "xenial"}) == "0.1.2-1" @flaky async def test_ubuntupkg_suite_with_paging(get_version): assert await get_version("ffmpeg", {"ubuntupkg": None, "suite": "xenial"}) == "7:2.8.15-0ubuntu0.16.04.1" nvchecker-1.4.3/tests/test_vcs.py000066400000000000000000000030161344563113400170410ustar00rootroot00000000000000# MIT licensed # Copyright (c) 2013-2017 lilydjwg , et al. import os import shutil import pytest pytestmark = pytest.mark.asyncio @pytest.mark.skipif(shutil.which("git") is None, reason="requires git command") async def test_git(get_version): os.path.exists("example") or os.mkdir("example") assert await get_version("example", {"vcs": "git+https://github.com/harry-sanabria/ReleaseTestRepo.git"}) == "1.1.2b3cdf6134b07ae6ac77f11b586dc1ae6d1521db" @pytest.mark.skipif(shutil.which("hg") is None, reason="requires hg command") async def test_mercurial(get_version): os.path.exists("example") or os.mkdir("example") assert await get_version("example", {"vcs": "hg+https://bitbucket.org/pil0t/testrepo"}) == "1.1.84679e29c7d9" @pytest.mark.skipif(shutil.which("git") is None, reason="requires git command") async def test_git_max_tag(get_version): os.path.exists("example") or os.mkdir("example") assert await get_version("example", {"vcs": "git+https://github.com/harry-sanabria/ReleaseTestRepo.git", "use_max_tag": 1}) == "second_release" @pytest.mark.skipif(shutil.which("git") is None, reason="requires git command") async def test_git_max_tag_with_ignored_tags(get_version): os.path.exists("example") or os.mkdir("example") assert await get_version("example", {"vcs": "git+https://github.com/harry-sanabria/ReleaseTestRepo.git", "use_max_tag": 1, "ignored_tags": "second_release release3"}) == "first_release"