Source code for cupage.cmdline

#
# coding=utf-8
"""command_line- Command line interface for cupage"""
# Copyright © 2009-2013  James Rowe <jnrowe@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

# This has to be here, as cupage uses 2.6 features.
import sys
if sys.version_info[:2] < (2, 6):
    print('Python v2.6, or later, is *required* for cupage!')
    sys.exit(1)


import atexit
import errno
import logging
import os
import re
import socket

from operator import attrgetter

try:
    # For Python 3
    import configparser
except ImportError:
    import ConfigParser as configparser  # NOQA

import argparse
import aaargh

import cupage

from .i18n import _
from . import (_version, utils)


#: Command line help string, for use with :mod:`argparse`
# Pull the first paragraph from the docstring
USAGE = cupage.__doc__[:cupage.__doc__.find('\n\n', 100)].splitlines()[2:]
# Replace script name with optparse's substitution var, and rebuild string
USAGE = '\n'.join(USAGE).replace('cupage', '%(prog)s')

APP = aaargh.App(description=USAGE,
                 epilog=_('Please report bugs to jnrowe@gmail.com'))


def frequency_typecheck(string):
    if utils.parse_timedelta(string):
        return string
    else:
        raise argparse.ArgumentTypeError('Invalid frequency value')


def load_sites(config, database, pages):
    if database is None:
        database = '%s%sdb' % (os.path.splitext(config)[0], os.path.extsep)

    sites = cupage.Sites()
    try:
        sites.load(config, database)
    except IOError as e:
        print(utils.fail(e.message))
        return errno.EIO
    except ValueError:
        print(utils.fail(_('Error reading database file')))
        return errno.ENOMSG
    except configparser.ParsingError:
        print(utils.fail(_('Error reading config file')))
        return errno.ENOENT

    # Check all named pages exist in config
    site_names = list(map(attrgetter('name'), sites))
    for page in pages:
        if page not in site_names:
            raise ValueError(_('Invalid site argument %r') % page)

    return sites


@APP.cmd(help='add definition to config file')
@APP.cmd_arg('-f', '--config', metavar='~/.cupage.conf',
             default=os.path.expanduser('~/.cupage.conf'),
             help=_('config file to read page definitions from'))
@APP.cmd_arg('-s', '--site', choices=cupage.SITES.keys(),
             help=_('site helper to use'))
@APP.cmd_arg('-u', '--url', metavar='url', help=_('site url to check'))
@APP.cmd_arg('-t', '--match-type', default='tar',
             choices=['gem', 're', 'tar', 'zip'],
             help=_('pre-defined regular expression to use'))
@APP.cmd_arg('-m', '--match', metavar='regex',
             help=_('regular expression to use with --match-type=re'))
@APP.cmd_arg('-q', '--frequency', metavar='frequency',
             type=frequency_typecheck, help='update check frequency')
@APP.cmd_arg('-x', '--select', metavar='selector', help=_('content selector'))
@APP.cmd_arg('--selector', default='css', choices=['css', 'xpath'],
             help=_('selector method to use'))
@APP.cmd_arg('name', help=_('site name'))
[docs]def add(verbose, config, site, url, match_type, match, frequency, select, selector, name): conf = configparser.ConfigParser() conf.read(config) conf.add_section(name) data = { 'site': site, 'url': url, 'match_type': match_type, 'match': match, 'frequency': frequency, 'select': select, 'selector': selector, } # Don't store unused values for key, value in data.items(): if value: conf.set(name, key, value) conf.write(open(config, 'w'))
@APP.cmd(help='check sites for updates') @APP.cmd_arg('-f', '--config', metavar='~/.cupage.conf', default=os.path.expanduser('~/.cupage.conf'), help=_('config file to read page definitions from')) @APP.cmd_arg('-d', '--database', metavar='~/.cupage.db', help=_('database to store page data to(default based on --config ' 'value)')) @APP.cmd_arg('-c', '--cache', metavar='~/.cupage/', default=os.path.expanduser('~/.cupage/'), help=_('directory to store page cache')) @APP.cmd_arg('--no-write', action='store_true', help=_("don't update cache or database")) @APP.cmd_arg('--force', action='store_true', help=_('ignore frequency checks')) @APP.cmd_arg('-t', '--timeout', type=int, metavar='30', default=30, help=_('timeout for network operations')) @APP.cmd_arg('pages', nargs='*', help=_('pages to check'))
[docs]def check(verbose, config, database, cache, no_write, force, timeout, pages): sites = load_sites(config, database, pages) if not no_write: if database is None: database = '%s%sdb' % (os.path.splitext(config)[0], os.path.extsep) atexit.register(sites.save, database) for site in sorted(sites, key=attrgetter('name')): if not pages or site.name in pages: if verbose: print(site) print(_('Checking %s...') % site.name) matches = site.check(cache, timeout, force, no_write) if matches: if verbose: print(_('%s has new matches') % site.name) for match in utils.sort_packages(matches): print(utils.success(match)) else: if verbose: print(_('%s has no new matches') % site.name)
@APP.cmd(name='list', help='list definitions from config file') @APP.cmd_arg('-f', '--config', metavar='~/.cupage.conf', default=os.path.expanduser('~/.cupage.conf'), help=_('config file to read page definitions from')) @APP.cmd_arg('-d', '--database', metavar='~/.cupage.db', help=_('database to store page data to(default based on --config ' 'value)')) @APP.cmd_arg('-m', '--match', metavar='regex', type=re.compile, help=_('match sites using regular expression')) @APP.cmd_arg('pages', nargs='*', help=_('pages to display'))
[docs]def list_conf(verbose, config, database, match, pages): sites = load_sites(config, database, pages) for site in sorted(sites, key=attrgetter('name')): if not pages and not match: print(site) elif pages and site.name in pages: print(site) elif match and match.search(site.name): print(site)
@APP.cmd(name='list-sites', help='list supported site values')
[docs]def list_sites(verbose): if verbose: print(_('Supported site values and their non-standard values:')) print() for site, values in sorted(cupage.SITES.items()): print('- %s (v%s)' % (site, values['added'])) if 'keys' in values: for item in values['keys'].items(): print(' * %s - %s' % item)
@APP.cmd(help='remove site from config') @APP.cmd_arg('-f', '--config', metavar='~/.cupage.conf', default=os.path.expanduser('~/.cupage.conf'), help=_('config file to read page definitions from')) @APP.cmd_arg('pages', nargs='*', help=_('pages to remove'))
[docs]def remove(verbose, config, pages): conf = configparser.ConfigParser() conf.read(config) if pages: for page in pages: if not conf.has_section(page): print(utils.fail(_('Invalid site argument %r') % page)) return False for page in pages: if verbose: print(_('Removing %s...') % page) conf.remove_section(page) conf.write(open(config, 'w'))
[docs]def main(): """Main script handler.""" logging.basicConfig(format='%(asctime)s %(levelname)s:%(message)s', datefmt='%Y-%m-%dT%H:%M:%S%z') APP.arg('--version', action='version', version='%%(prog)s %s' % _version.dotted) APP.arg('-v', '--verbose', action='store_true', dest='verbose', help=_('produce verbose output')) APP.arg('-q', '--quiet', action='store_false', dest='verbose', help=_('output only matches and errors')) try: APP.run() except socket.error as error: print(utils.fail(error.strerror or error.message)) return errno.EADDRNOTAVAIL except (configparser.DuplicateSectionError, IOError) as error: print(utils.fail(error.message)) return errno.ENOENT