diff options
-rw-r--r-- | SBoTools/README.md | 1 | ||||
-rw-r--r-- | SBoTools/__init__.py | 48 | ||||
-rw-r--r-- | SBoTools/config.py | 42 | ||||
-rw-r--r-- | SBoTools/plugin.py | 177 | ||||
-rw-r--r-- | SBoTools/test.py | 15 | ||||
-rwxr-xr-x | bin/sbodb.pl | 123 |
6 files changed, 406 insertions, 0 deletions
diff --git a/SBoTools/README.md b/SBoTools/README.md new file mode 100644 index 0000000..a694e68 --- /dev/null +++ b/SBoTools/README.md @@ -0,0 +1 @@ +Provides Slackware package database lookups. diff --git a/SBoTools/__init__.py b/SBoTools/__init__.py new file mode 100644 index 0000000..fe66e85 --- /dev/null +++ b/SBoTools/__init__.py @@ -0,0 +1,48 @@ +### +# Copyright (c) 2021, B. Watson +# All rights reserved. +# +# +### + +""" +SlackTools: Provides Unix manpage lookups +""" + +import sys +import supybot +from supybot import world + +# Use this for the version of this plugin. +__version__ = "0.0.1" + +# XXX Replace this with an appropriate author or supybot.Author instance. +__author__ = supybot.Author('B. Watson', 'Urchlay', 'yalhcru@gmail.com') + +# This is a dictionary mapping supybot.Author instances to lists of +# contributions. +__contributors__ = {} + +# This is a url where the most recent plugin package can be downloaded. +__url__ = '' + +from . import config +from . import plugin +if sys.version_info >= (3, 4): + from importlib import reload +else: + from imp import reload +# In case we're being reloaded. +reload(config) +reload(plugin) +# Add more reloads here if you add third-party modules and want them to be +# reloaded when this plugin is reloaded. Don't forget to import them as well! + +if world.testing: + from . import test + +Class = plugin.Class +configure = config.configure + + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/SBoTools/config.py b/SBoTools/config.py new file mode 100644 index 0000000..b13dc28 --- /dev/null +++ b/SBoTools/config.py @@ -0,0 +1,42 @@ +### +# Copyright (c) 2021, B. Watson +# All rights reserved. +# +# +### + +from supybot import conf, registry +try: + from supybot.i18n import PluginInternationalization + _ = PluginInternationalization('SBoTools') +except: + # Placeholder that allows to run the plugin on a bot + # without the i18n module + _ = lambda x: x + + +def configure(advanced): + # This will be called by supybot to configure this module. advanced is + # a bool that specifies whether the user identified themself as an advanced + # user or not. You should effect your configuration by manipulating the + # registry as appropriate. + from supybot.questions import expect, anything, something, yn + conf.registerPlugin('SBoTools', True) + + +SBoTools = conf.registerPlugin('SBoTools') +# This is where your configuration variables (if any) should go. For example: +# conf.registerGlobalValue(SBoTools, 'someConfigVariableName', +# registry.Boolean(False, _("""Help for someConfigVariableName."""))) + +conf.registerGlobalValue(SBoTools, 'dbpath', + registry.String("/home/slackfacts/supybot/SBoTools.sqlite3", _("""Path to sqite3 database."""))) + +conf.registerGlobalValue(SBoTools, 'maxresults', + registry.Integer(5, _("""Maximum number of results to send to client."""))) + +## conf.registerGlobalValue(SBoTools, 'slackpath', +## registry.String("/data/mirrors/slackware/slackware64-14.2", _("""Filesystem path to Slackware mirror (NO trailing slash!)"""))) +## +## conf.registerGlobalValue(SBoTools, 'baseurl', +## registry.String("https://slackware.uk/slackware/slackware64-14.2", _("""Web URL of Slackware mirror (NO trailing slash!)"""))) diff --git a/SBoTools/plugin.py b/SBoTools/plugin.py new file mode 100644 index 0000000..0e18e9d --- /dev/null +++ b/SBoTools/plugin.py @@ -0,0 +1,177 @@ +### +# Copyright (c) 2021, B. Watson +# All rights reserved. +# +# +### + +# TODO: add maintainer name/email to the DB. + +import os +import glob +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('SBoTools') +except ImportError: + # Placeholder that allows to run the plugin on a bot + # without the i18n module + _ = lambda x: x + +class SBoTools(callbacks.Plugin): + """Provides SlackBuilds.org build searches""" + threaded = True + + db = None + categories = 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 getCategory(self, cat): + if self.categories is None: + self.categories = {} + db = self.InitDB(); + cursor = db.cursor() + cursor.execute("select id, name from categories"); + result = cursor.fetchall() + for (id, name) in result: + self.categories[name] = id + + if cat in self.categories: + return self.categories[cat] + else: + return None + + def sbosearch(self, irc, msg, args, tflag, terms): + """ [<category/>]<build> | [-t] <searchterm> [<searchterm> ...]> + + Search the SlackBuilds.org build database. Case-insensitive + substring match. Without -t, does a search by name only. With + -t, searches names, descriptions, and tags. With multiple search + terms, results are ANDed together. + """ + + category = None + if(not tflag): + if(len(terms) > 1): + irc.error("only one search term allowed unless using -t flag", Raise=True) + t = terms[0].split('/',1) + if(len(t) != 1): + category = self.getCategory(t[0]) + if(category is None): + irc.error("invalid category '" + t[0] + "'", Raise=True) + terms[0] = t[1] + + + db = self.InitDB(); + cursor = db.cursor() + tags = False; + + sql = "select distinct(b.id) from builds b, tags t " + sql += "where b.id = t.build_id" + + if(category is not None): + sql += " and b.category=" + str(category) + " " + + for (term) in terms: + tlike = "'%" + term.replace("'", "''") + "%'" + if(tflag): + sql += " and (b.name like " + tlike + " or b.descrip like " + tlike + " or t.tag like " + tlike + ")" + else: + sql += " and b.name like " + tlike + + maxresults = self.getMaxResults(msg) + sql += " limit " + str(maxresults + 1) + + cursor.execute(sql) + result = cursor.fetchall() + if(len(result) == 0): + irc.reply("no results found") + else: + lines = [] + for (i) in result: + c2 = db.cursor() + c2.execute("select b.name, c.name from builds b, categories c where b.category=c.id and b.id=?", (i[0],)) + xres = c2.fetchall(); + if(len(result) == 1): + (name, cat) = xres[0] + #irc.reply("would be showing !sboinfo " + cat + "/" + name); + self.SBoInfo(irc, msg, cat + "/" + name) + return; + for (name, cat) in xres: + lines.append(cat + "/" + name) + + if(len(result) > maxresults): + lines.append("[too many results, only showing first " + str(maxresults) + "]") + irc.replies(lines, joiner=' | ') + + sbosearch = thread(wrap(sbosearch, [optional(('literal', ['-t'])), many('somethingWithoutSpaces')])) + + def SBoInfo(self, irc, msg, catbuild): + category = None + build = None + + t = catbuild.split('/',1) + if(len(t) == 1): + build = catbuild + else: + category = self.getCategory(t[0]) + if(category is None): + irc.error("invalid category '" + t[0] + "'", Raise=True) + build = t[1] + + db = self.InitDB(); + cursor = db.cursor() + if(category is None): + cursor.execute("select b.id, b.name, c.name, b.descrip, b.version from builds b, categories c where b.category = c.id and b.name = ?", (build,)) + else: + cursor.execute("select b.id, b.name, c.name, b.descrip, b.version from builds b, categories c where c.id = ? and b.category = c.id and b.name = ?", (category, build,)) + + result = cursor.fetchall() + if(len(result) == 0): + irc.reply("no results found") + else: + lines = [] + for (bid, bname, cname, bdescrip, bversion) in result: + lines.append(cname + "/" + bname + " v" + bversion + ": " + bdescrip) + cursor.execute("select b.name from builds b, deps d where d.build_id=? and b.id=d.depends_on", (bid,)) + depres = cursor.fetchall() + if(len(depres) != 0): + deps = "" + for (depname) in depres: + deps += depname[0] + " " + lines.append("deps: " + deps) + + irc.replies(lines, joiner=' | ') + + def sboinfo(self, irc, msg, args, catbuild): + """ [<category/>]<build> + + Show detailed information about a SBo build. The argument must be + an exact match (this is not a search). + """ + self.SBoInfo(irc, msg, catbuild) + + sboinfo = thread(wrap(sboinfo, ['somethingWithoutSpaces'])) + +Class = SBoTools diff --git a/SBoTools/test.py b/SBoTools/test.py new file mode 100644 index 0000000..14d770b --- /dev/null +++ b/SBoTools/test.py @@ -0,0 +1,15 @@ +### +# Copyright (c) 2021, B. Watson +# All rights reserved. +# +# +### + +from supybot.test import * + + +class SlackToolsTestCase(PluginTestCase): + plugins = ('SlackTools',) + + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/bin/sbodb.pl b/bin/sbodb.pl new file mode 100755 index 0000000..bfd997e --- /dev/null +++ b/bin/sbodb.pl @@ -0,0 +1,123 @@ +#!/usr/bin/perl -w + +# create SBo.sqlite3 database for SBo limnoria plugin. +# requires SLACKBUILDS.txt and TAGS.txt from the SBo repo. +# https://slackware.uk/slackbuilds.org/14.2/SLACKBUILDS.TXT +# https://slackware.uk/slackbuilds.org/14.2/TAGS.txt + +print <<EOF; +pragma journal_mode = memory; +begin transaction; + +create table categories ( + id integer primary key not null, + name varchar not null); + +create table builds ( + id integer primary key not null, + name varchar not null, + descrip varchar not null, + category integer not null, + version varchar not null, + foreign key (category) references categories); + +create table deps ( + build_id integer not null, + depends_on integer not null, + foreign key (build_id) references categories, + foreign key (depends_on) references categories); + +create table tags ( + build_id integer not null, + tag varchar not null, + foreign key (build_id) references builds); + +create unique index t_idx on tags(build_id, tag); + +EOF + +$lastcat = 0; +sub get_cat_id { + my $catname = shift; + if(!$catids{$catname}) { + $lastcat++; + print "insert into categories values($lastcat, '$catname');\n"; + $catids{$catname} = $lastcat; + } + return $catids{$catname}; +} + +open $sbtxt, "<" . ($ARGV[0] || "SLACKBUILDS.TXT") or die $!; +{ + local $/ = ''; + while(<$sbtxt>) { + my ($name, $cat, $ver, $deps, $desc,); + $deps = ""; + chomp; + /^SLACKBUILD NAME:\s+(\S+)$/m and $name = $1; + /^SLACKBUILD LOCATION:\s+\.\/([^\/]*)\//m and $cat = $1; + /^SLACKBUILD VERSION:\s+(\S+)$/m and $ver = $1; + /^SLACKBUILD REQUIRES:\s+(\S.+)\n/m and $deps = $1; + /^SLACKBUILD SHORT DESCRIPTION:\s+(.+)$/m and $desc = $1; + + if($desc =~ /^$name \((.+)\)$/) { + $desc = $1; + } + + $desc =~ s/'/''/g; + $catid = get_cat_id($cat); + $buildcat{$name} = $catid; + $buildver{$name} = $ver; + $builddeps{$name} = $deps; + $builddesc{$name} = $desc; + push @builds, $name; + } +} +close $sbtxt; + +print "\n"; + +$buildid = 0; +for(@builds) { + $buildid++; + $buildids{$_} = $buildid; + print <<EOF; +insert into builds values( + $buildid, + '$_', + '$builddesc{$_}', + $buildcat{$_}, + '$buildver{$_}'); + +EOF +} + +for(keys %builddeps) { + my $bid = $buildids{$_}; + my @d = split /\s+/, $builddeps{$_}; + #warn "build $_ id $b, deps " . join(",", @d) . "\n"; + for(@d) { + next if /%README%/; + print <<EOF; +insert into deps values($bid, $buildids{$_}); +EOF + } +} + +open $tagstxt, "<" . ($ARGV[1] || "TAGS.txt") or die $!; +while(<$tagstxt>) { + my ($build, $t, @tags); + chomp; + next if /: No tags found for/; + ($build, $t) = /^([^:]*):\s+(.*)$/; + @tags = split /,/, $t; + for(@tags) { + next if /^\s*$/; + s/'/''/g; + print <<EOF; +insert into tags values($buildids{$build}, '$_'); +EOF + } +} + +print "commit;\n"; |