mirror of
https://github.com/lilydjwg/nvchecker.git
synced 2025-03-10 06:14:02 +00:00
version 0.4: simpler config, simpler usage
and simpler code
This commit is contained in:
parent
9f0e030958
commit
dfaf858951
11 changed files with 194 additions and 191 deletions
36
README.rst
36
README.rst
|
@ -6,6 +6,7 @@ Dependency
|
|||
==========
|
||||
- Python 3
|
||||
- Tornado
|
||||
- Optional pycurl
|
||||
- All commands used in your version source files
|
||||
|
||||
Running
|
||||
|
@ -16,21 +17,21 @@ To see available options::
|
|||
|
||||
Run with one or more software version source files::
|
||||
|
||||
./nvchecker source_file_1 source_file_2 ...
|
||||
./nvchecker source_file
|
||||
|
||||
You normally will like to specify some "version record files"; see below.
|
||||
|
||||
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)`` seperated by a space\ [#]_::
|
||||
Version record files record which version of the software you know or is available. They are simple key-value pairs of ``(name, version)`` seperated by a space\ [v0.3]_::
|
||||
|
||||
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. To update it using ``nvchecker``::
|
||||
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 --oldver old_ver.txt --newver new_ver.txt source.ini
|
||||
./nvchecker source.ini
|
||||
|
||||
Compare the two files for updates (assuming they are sorted alphabetically; files generated by ``nvchecker`` are already sorted)::
|
||||
|
||||
|
@ -54,6 +55,20 @@ The software version source files are in ini format. *Section names* is the name
|
|||
|
||||
See ``sample_source.ini`` for an example.
|
||||
|
||||
Configuration Section
|
||||
---------------------
|
||||
A special section named ``__config__`` is special, it provides some configuration options\ [v0.4]_.
|
||||
|
||||
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 storing the new version info.
|
||||
|
||||
Search in a Webpage
|
||||
-------------------
|
||||
Search through a specific webpage for the version string. This type of version finding has these fields:
|
||||
|
@ -120,16 +135,6 @@ Other
|
|||
-----
|
||||
More to come. Send me a patch or pull request if you can't wait and have written one yourself :-)
|
||||
|
||||
Config File
|
||||
===========
|
||||
``nvchecker`` supports a config file, which contains whatever you would give on commandline every time. This file is at ``~/.nvcheckerrc`` by default, and can be changed by the ``-c`` option. You can specify ``-c /dev/null`` to disable the default config file temporarily.
|
||||
|
||||
A typical config file looks like this::
|
||||
|
||||
--oldver ~/.nvchecker/versionlist.txt --newver ~/.nvchecker/versionlist_new.txt
|
||||
|
||||
``~`` and environmental variables will be expanded. Options given on commandline override those in a config file.
|
||||
|
||||
Bugs
|
||||
====
|
||||
* Finish writing results even on Ctrl-C or other interruption.
|
||||
|
@ -141,4 +146,5 @@ TODO
|
|||
|
||||
Footnotes
|
||||
=========
|
||||
.. [#] Note: with nvchecker <= 0.2, there are one more colon each line. You can use ``sed -i 's/://' FILES...`` to remove them.
|
||||
.. [v0.3] Note: with nvchecker <= 0.2, there are one more colon each line. You can use ``sed -i 's/://' FILES...`` to remove them.
|
||||
.. [v0.4] This is added in version 0.4, and old command-line options are removed.
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = '0.3'
|
||||
__version__ = '0.4'
|
||||
|
|
123
nvchecker/core.py
Normal file
123
nvchecker/core.py
Normal file
|
@ -0,0 +1,123 @@
|
|||
# vim: se sw=2:
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import configparser
|
||||
|
||||
from pkg_resources import parse_version
|
||||
|
||||
from .lib import nicelogger
|
||||
from .get_version import get_version
|
||||
|
||||
from . import __version__
|
||||
|
||||
logger = logging.getLogger(__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('-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'''
|
||||
nicelogger.enable_pretty_logging(getattr(logging, args.logging.upper()))
|
||||
|
||||
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)
|
||||
# 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 = {}
|
||||
with open(file) as f:
|
||||
for l in f:
|
||||
name, ver = l.rstrip().split(None, 1)
|
||||
v[name] = ver
|
||||
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:
|
||||
started = False
|
||||
tasks = 0
|
||||
def __init__(self, file):
|
||||
self.config = config = configparser.ConfigParser(
|
||||
dict_type=dict, allow_no_value=True
|
||||
)
|
||||
self.name = file.name
|
||||
config.read_file(file)
|
||||
if '__config__' in config:
|
||||
c = config['__config__']
|
||||
d = os.path.dirname(file.name)
|
||||
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'))))
|
||||
|
||||
def check(self):
|
||||
self.started = True
|
||||
|
||||
if self.oldver:
|
||||
self.oldvers = read_verfile(self.oldver)
|
||||
else:
|
||||
self.oldvers = {}
|
||||
self.curvers = self.oldvers.copy()
|
||||
|
||||
config = self.config
|
||||
for name in config.sections():
|
||||
if name == '__config__':
|
||||
continue
|
||||
self.task_inc()
|
||||
get_version(name, config[name], self.print_version_update)
|
||||
|
||||
def task_inc(self):
|
||||
self.tasks += 1
|
||||
|
||||
def task_dec(self):
|
||||
self.tasks -= 1
|
||||
if self.tasks == 0 and self.started:
|
||||
if self.newver:
|
||||
write_verfile(self.newver, self.curvers)
|
||||
self.on_finish()
|
||||
|
||||
def print_version_update(self, name, version):
|
||||
try:
|
||||
if version is None:
|
||||
return
|
||||
|
||||
oldver = self.oldvers.get(name, None)
|
||||
if not oldver or parse_version(oldver) < parse_version(version):
|
||||
logger.info('%s updated version %s', name, version)
|
||||
self.curvers[name] = version
|
||||
self.on_update(name, version, oldver)
|
||||
else:
|
||||
logger.debug('%s current version %s', name, version)
|
||||
finally:
|
||||
self.task_dec()
|
||||
|
||||
def on_update(self, name, version, oldver):
|
||||
pass
|
||||
|
||||
def on_finish(self):
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return '<Source from %r>' % self.name
|
|
@ -1,104 +1,44 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import configparser
|
||||
import logging
|
||||
import argparse
|
||||
|
||||
from pkg_resources import parse_version
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
from .lib import notify
|
||||
|
||||
from .get_version import get_version
|
||||
from . import util
|
||||
from . import core
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
notifications = []
|
||||
g_counter = 0
|
||||
g_oldver = {}
|
||||
g_curver = {}
|
||||
args = None
|
||||
|
||||
def task_inc():
|
||||
global g_counter
|
||||
g_counter += 1
|
||||
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 task_dec():
|
||||
global g_counter
|
||||
g_counter -= 1
|
||||
if g_counter == 0:
|
||||
def on_finish(self):
|
||||
IOLoop.instance().stop()
|
||||
write_verfile()
|
||||
|
||||
def load_config(*files):
|
||||
config = configparser.ConfigParser(
|
||||
dict_type=dict, allow_no_value=True
|
||||
)
|
||||
for file in files:
|
||||
with open(file) as f:
|
||||
config.read_file(f)
|
||||
|
||||
return config
|
||||
|
||||
def write_verfile():
|
||||
if not args.newver:
|
||||
return
|
||||
util.write_verfile(args.newver, g_curver)
|
||||
|
||||
def print_version_update(name, version):
|
||||
if version is None:
|
||||
task_dec()
|
||||
return
|
||||
|
||||
oldver = g_oldver.get(name, None)
|
||||
if not oldver or parse_version(oldver) < parse_version(version):
|
||||
logger.info('%s updated version %s', name, version)
|
||||
_updated(name, version)
|
||||
else:
|
||||
logger.info('%s current version %s', name, version)
|
||||
task_dec()
|
||||
|
||||
def _updated(name, version):
|
||||
g_curver[name] = version
|
||||
|
||||
if args.notify:
|
||||
msg = '%s updated to version %s' % (name, version)
|
||||
notifications.append(msg)
|
||||
notify.update('nvchecker', '\n'.join(notifications))
|
||||
|
||||
def get_versions(config):
|
||||
task_inc()
|
||||
for name in config.sections():
|
||||
task_inc()
|
||||
get_version(name, config[name], print_version_update)
|
||||
task_dec()
|
||||
|
||||
def main():
|
||||
global args
|
||||
|
||||
parser = argparse.ArgumentParser(description='New version checker for software')
|
||||
parser.add_argument('files', metavar='FILE', nargs='*',
|
||||
help='software version source files')
|
||||
parser.add_argument('-n', '--notify', action='store_true', default=False,
|
||||
help='show desktop notifications when a new version is available')
|
||||
util.add_common_arguments(parser)
|
||||
|
||||
args = util.parse_args(parser)
|
||||
if util.process_common_arguments(args):
|
||||
core.add_common_arguments(parser)
|
||||
args = parser.parse_args()
|
||||
if core.process_common_arguments(args):
|
||||
return
|
||||
|
||||
if not args.files:
|
||||
if not args.file:
|
||||
return
|
||||
|
||||
def run():
|
||||
config = load_config(*args.files)
|
||||
if args.oldver:
|
||||
g_oldver.update(util.read_verfile(args.oldver))
|
||||
g_curver.update(g_oldver)
|
||||
get_versions(config)
|
||||
s = Source(args.file)
|
||||
|
||||
ioloop = IOLoop.instance()
|
||||
ioloop.add_callback(run)
|
||||
ioloop.add_callback(s.check)
|
||||
ioloop.start()
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -36,11 +36,12 @@ def get_version(name, conf, callback):
|
|||
), **kwargs)
|
||||
|
||||
def _got_version(name, regex, encoding, callback, res):
|
||||
body = res.body.decode(encoding)
|
||||
version = None
|
||||
try:
|
||||
version = max(regex.findall(body), key=parse_version)
|
||||
except ValueError:
|
||||
logger.error('%s: version string not found.', name)
|
||||
callback(name, None)
|
||||
else:
|
||||
body = res.body.decode(encoding)
|
||||
try:
|
||||
version = max(regex.findall(body), key=parse_version)
|
||||
except ValueError:
|
||||
logger.error('%s: version string not found.', name)
|
||||
finally:
|
||||
callback(name, version)
|
||||
|
|
|
@ -2,26 +2,31 @@
|
|||
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from . import util
|
||||
from . import core
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def take():
|
||||
parser = argparse.ArgumentParser(description='update version records of nvchecker')
|
||||
core.add_common_arguments(parser)
|
||||
parser.add_argument('names', metavar='NAME', nargs='*',
|
||||
help='software name to be updated')
|
||||
util.add_common_arguments(parser)
|
||||
|
||||
args = util.parse_args(parser)
|
||||
if util.process_common_arguments(args):
|
||||
args = parser.parse_args()
|
||||
if core.process_common_arguments(args):
|
||||
return
|
||||
|
||||
if not args.oldver or not args.newver:
|
||||
sys.exit('You must specify old and new version records so that I can update.')
|
||||
s = core.Source(args.file)
|
||||
if not s.oldver or not s.newver:
|
||||
logger.error(
|
||||
"%s doesn't have both 'oldver' and 'newver' set, ignoring.", s
|
||||
)
|
||||
|
||||
oldvers = util.read_verfile(args.oldver)
|
||||
newvers = util.read_verfile(args.newver)
|
||||
oldvers = core.read_verfile(s.oldver)
|
||||
newvers = core.read_verfile(s.newver)
|
||||
|
||||
for name in args.names:
|
||||
oldvers[name] = newvers[name]
|
||||
|
||||
util.write_verfile(args.oldver, oldvers)
|
||||
core.write_verfile(s.oldver, oldvers)
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
# vim: se sw=2:
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
|
||||
from .lib import nicelogger
|
||||
|
||||
from . import __version__
|
||||
|
||||
_DEFAULT_CONFIG = os.path.expanduser('~/.nvcheckerrc')
|
||||
|
||||
def add_common_arguments(parser):
|
||||
parser.add_argument('-i', '--oldver',
|
||||
help='read an existing version record file')
|
||||
parser.add_argument('-o', '--newver',
|
||||
help='write a new version record file')
|
||||
parser.add_argument('-c', metavar='CONFIG_FILE', default=_DEFAULT_CONFIG,
|
||||
help='specify the nvcheckerrc file to use')
|
||||
parser.add_argument('-l', '--logging',
|
||||
choices=('debug', 'info', 'warning', 'error'), default='info',
|
||||
help='logging level (default: info)')
|
||||
parser.add_argument('-V', '--version', action='store_true',
|
||||
help='show version and exit')
|
||||
|
||||
def _get_rcargs():
|
||||
args = sys.argv[1:]
|
||||
args.reverse()
|
||||
it = iter(args)
|
||||
|
||||
try:
|
||||
f = next(it)
|
||||
while True:
|
||||
j = next(it)
|
||||
if j == '-c':
|
||||
break
|
||||
f = j
|
||||
except StopIteration:
|
||||
if os.path.exists(_DEFAULT_CONFIG):
|
||||
f = _DEFAULT_CONFIG
|
||||
else:
|
||||
return []
|
||||
|
||||
return [os.path.expandvars(os.path.expanduser(x))
|
||||
for x in open(f, 'r').read().split()]
|
||||
|
||||
def parse_args(parser):
|
||||
args = _get_rcargs()
|
||||
args += sys.argv[1:]
|
||||
return parser.parse_args(args)
|
||||
|
||||
def process_common_arguments(args):
|
||||
'''return True if should stop'''
|
||||
nicelogger.enable_pretty_logging(getattr(logging, args.logging.upper()))
|
||||
|
||||
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)
|
||||
# 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 = {}
|
||||
with open(file) as f:
|
||||
for l in f:
|
||||
name, ver = l.rstrip().split(None, 1)
|
||||
v[name] = ver
|
||||
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')
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
# my AUR packages
|
||||
[__config__]
|
||||
oldver = ../records/arch_aur.txt
|
||||
newver = ../records/arch_aur.new.txt
|
||||
|
||||
[cld2-svn]
|
||||
url = https://code.google.com/p/cld2/source/list
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
# Arch Linux CN repository
|
||||
[__config__]
|
||||
oldver = ../records/arch_cn.txt
|
||||
newver = ../records/arch_cn.new.txt
|
||||
|
||||
[aliedit]
|
||||
aur =
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# lilydjwg repository:
|
||||
# https://bbs.archlinuxcn.org/viewtopic.php?id=1695
|
||||
[__config__]
|
||||
oldver = ../records/arch_lilydjwg.txt
|
||||
newver = ../records/arch_lilydjwg.new.txt
|
||||
|
||||
[3to2]
|
||||
aur
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
[__config__]
|
||||
oldver = old_ver.txt
|
||||
newver = new_ver.txt
|
||||
|
||||
[fcitx]
|
||||
url = https://code.google.com/p/fcitx/
|
||||
regex = fcitx-([\d.]+)\.tar\.xz
|
||||
|
|
Loading…
Add table
Reference in a new issue