diff options
Diffstat (limited to 'SlackTools/plugin.py')
| -rw-r--r-- | SlackTools/plugin.py | 202 | 
1 files changed, 202 insertions, 0 deletions
| diff --git a/SlackTools/plugin.py b/SlackTools/plugin.py new file mode 100644 index 0000000..e4cc8bb --- /dev/null +++ b/SlackTools/plugin.py @@ -0,0 +1,202 @@ +### +# Copyright (c) 2021, B. Watson +# All rights reserved. +# +# +### + +import os +import subprocess +import sys +import re +import sqlite3 + +from supybot import utils, plugins, ircutils, callbacks +from supybot.commands import * +import supybot.utils.minisix as minisix + +try: +    from supybot.i18n import PluginInternationalization +    _ = PluginInternationalization('SlackTools') +except ImportError: +    # Placeholder that allows to run the plugin on a bot +    # without the i18n module +    _ = lambda x: x + +# This code could stand to be refactored. Lots of almost-identical +# code in FileQuery and PkgQuery. Also, I'm pretty much a python +# n00b, so the code might read a little weird if you're a python +# expert (I learned just enough of the language, on the fly, to +# get this stuff written). That's also why the database-creation +# script is in perl (I'm much more familiar and competent with perl). + +# notes about the database: + +# it gets created by the makepkgdb.pl script, which requires: +# - PACKAGES.txt and extra/PACKAGES.txt from the installer. +# - copies of /var/log/scripts and /var/log/packages from a full +#   slackware install (including /extra but not /testing nor +#   /pasture) with no 3rd-party packages installed. +# see the comments in makepkgdb.pl for more info. + +# The database only contains packages from one slackware version +# and arch. Currently that's x86_64 14.2. Only packages from the +# main slackware64/ and extra/ trees are in the db. If there's any +# demand for it, it wouldn't be hard to support multiple slack +# versions and/or arches... or add pasture/ and/or testing/ to the db. + +# It would have been easy to include package sizes in the db, but +# I didn't think it would be that useful. + +# The files table also includes symlinks. + +# /lib64/incoming/ paths get their /incoming removed before being +# inserted in the db. + +# file paths are stored *with* the leading slash, so e.g. /bin/ls +# rather than the bin/ls. This is unlike usual slackpkg and +# /var/log/packages usage, but it seems more intuitive for users. + + +class SlackTools(callbacks.Plugin): +    """Provides Slackware package db lookups""" +    threaded = True + +    db = None + +    def getMaxResults(self, msg): +        maxresults = self.registryValue('maxresults') +        if msg.channel is None: +            # private message, increase limit +            maxresults *= 5 +        return maxresults + +    def InitDB(self): +        if self.db is None: +            filename = self.registryValue('dbpath') +            if(os.path.exists(filename)): +                self.db = sqlite3.connect(filename) +        return self.db + +    def PkgQuery(self, irc, msg, searchtype, term): +        db = self.InitDB(); +        if db is None: +            irc.error("slackpkg database doesn't exist", Raise=True) + +        maxresults = self.getMaxResults(msg) +        cursor = db.cursor() +        if(searchtype == 'pkg'): +            cursor.execute("select c.name, p.name, p.descrip from categories c, packages p where c.id=p.category and p.name glob ? order by c.name, p.name limit ?", (term, maxresults+1)) +        else: +            cursor.execute("select distinct c.name, p.name, p.descrip from categories c, packages p, files f where c.id=p.category and p.id=f.package and f.path glob ? order by c.name, p.name limit ?", (term, maxresults+1)) + +        result = cursor.fetchall() + +        lines = [] + +        if len(result) == 0: +            irc.reply("no matching packages") +        else: +            for (category, pkg, descrip) in result: +                lines.append(ircutils.bold(category + "/" + pkg) + ": " + descrip) +            if(len(result) > maxresults): +                lines.append("[too many results, only showing first " + str(maxresults) + "]") +            irc.replies(lines, joiner=' | ') + + +    # search for packages by name +    def pkgsearch(self, irc, msg, args, pkg): +        """ <packagename> + +        Search the Slackware package database for packages named like <packagename>. +        This is a case-sensitive substring match (e.g. "core" matches "coreutils" +        and "xapian-core"). You can use * for globbing, e.g. "c*s" to find all +        packages whose names begin with "c" and end with "s". +        """ + +        self.PkgQuery(irc, msg, 'pkg', "*" + pkg + "*") + +    pkgsearch = thread(wrap(pkgsearch, ['somethingWithoutSpaces'])) + +    # search for packages by contents +    def filesearch(self, irc, msg, args, file): +        """ <filename> + +        Search the Slackware package database for packages that contain files +        named <filename>. This is a case-sensitive exact match, by default (e.g. +        "/bin/ls" does not match "/usr/bin/ls" nor "/bin/lsattr"). You can use * +        for globbing, e.g. "/bin/ls*" to match all files in /bin whose names begin +        with "ls". +        """ + +        self.PkgQuery(irc, msg, 'file', file) + +    filesearch = thread(wrap(filesearch, ['somethingWithoutSpaces'])) + +    # run filesearch or pkgsearch, save some typing. +    def pkg(self, irc, msg, args, fileflag, term): +        """ [-f] <file-or-package> + +        Without -f, same as pkgsearch. With -f, same as filesearch. +        See the help for pkgsearch and/or filesearch for details. +        """ + +        if(fileflag): +            self.PkgQuery(irc, msg, 'file', term) +        else: +            self.PkgQuery(irc, msg, 'pkg', "*" + term + "*") + +    pkg = thread(wrap(pkg, [optional(('literal', ['-f'])), 'somethingWithoutSpaces'])) + +    def FileQuery(self, irc, msg, term): +        db = self.InitDB(); +        if db is None: +            irc.error("slackpkg database doesn't exist", Raise=True) + +        maxresults = self.getMaxResults(msg) +        cursor = db.cursor() +        cursor.execute("select path, symlink from files where path glob ? order by path limit ?", (term, maxresults+1)) + +        result = cursor.fetchall() + +        lines = [] + +        if len(result) == 0: +            irc.reply("nothing found") +        else: +            for (file,symlink) in result: +                if(symlink): +                   file = file + " [l]" +                lines.append(file) +            if(len(result) > maxresults): +                lines.append("[too many results, only showing first " + str(maxresults) + "]") +            irc.replies(lines, joiner=' | ') + +    # subset of locate/slocate/mlocate. +    def locate(self, irc, msg, args, file): +        """ <filename> + +        Search the package database for files named like <filename>. +        This is a case-sensitive substring match (basically works like the +        actual 'locate' command). You can use * for globbing. +        """ + +        self.FileQuery(irc, msg, '*' + file + '*') + +    locate = thread(wrap(locate, ['somethingWithoutSpaces'])) + +    # faux 'which' command. +    def which(self, irc, msg, args, file): +        """ <filename> + +        Search the package database for files named like */bin/<filename>. +        This is similar to the OS "which" command, except it doesn't search +        a $PATH, it doesn't know whether the file has +x permission, it can +        return multiple results, and you can use * for globbing. +        """ + +        self.FileQuery(irc, msg, '*/bin/' + file) + +    which = thread(wrap(which, ['somethingWithoutSpaces'])) + +Class = SlackTools | 
