aboutsummaryrefslogtreecommitdiff
path: root/SlackTools/plugin.py
blob: e4cc8bb836a6a1e7d7ffe7a42f08031eeff60fd0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
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