summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/auth.js78
-rw-r--r--lib/bucky.js191
-rw-r--r--lib/db/bookshelf.js24
-rw-r--r--lib/db/index.js187
-rw-r--r--lib/fortune.js28
-rw-r--r--lib/index.js60
-rw-r--r--lib/middleware.js23
-rw-r--r--lib/router.js169
-rw-r--r--lib/search/index.js12
-rw-r--r--lib/search/snippet.js103
-rw-r--r--lib/util.js4
11 files changed, 113 insertions, 766 deletions
diff --git a/lib/auth.js b/lib/auth.js
deleted file mode 100644
index 38901e4..0000000
--- a/lib/auth.js
+++ /dev/null
@@ -1,78 +0,0 @@
-
-var passport = require('passport'),
- LocalStrategy = require('passport-local').Strategy,
- crypto = require('crypto'),
- db = require('./db');
-
-
-var auth = module.exports = {
-
- init: function(){
- passport.serializeUser(auth.serializeUser)
- passport.deserializeUser(auth.deserializeUser)
-
- passport.use(new LocalStrategy(auth.verifyLocalUser))
- },
-
- serializeUser: function (user, done) {
- done(null, user.id);
- },
-
- deserializeUser: function (id, done) {
- db.getUser(id).then(function(user){
- done(! user, user)
- })
- },
-
- validPassword: function(user, pw){
- var shasum = crypto.createHash('sha1')
- shasum.update(pw)
- return user.get('password') === shasum.digest('hex');
- },
-
- verifyLocalUser: function (username, password, done) {
- // handle passwords!!
- db.getUserByUsername(username).then(function(user){
-
- // if (err) { return done(err); }
- if (! user) { return done("no user") }
-
- return done(null, user)
-
- if (! user) {
- return done(null, false, { error: { errors: { username: { message: 'No such username.' } }}})
- }
- if (! auth.validPassword(user, password)) {
- return done(null, false, { error: { errors: { password: { message: 'Incorrect password.' } }}})
- }
- return done(null, user);
- })
- },
-
- loggedInLocal: function (req, res, next) {
- passport.authenticate("local", function(err, user, info){
- if (err) {
- return res.json({ error: err });
- }
- if (! user) {
- return info ? res.json(info) : res.redirect("/login");
- }
-
- // user.last_seen = new Date ()
- // user.save(function(err, data){ if (err) console.err('error setting ip for user') })
-
- req.logIn(user, function(err) {
- if (err) { return next(err); }
- var returnTo = req.session.returnTo
- delete req.session.returnTo
- return res.json({ status: "OK", returnTo: returnTo || "/index" })
- });
- })(req, res, next)
- },
-
- logout: function (req, res) {
- req.logout();
- res.redirect('/');
- },
-
-} \ No newline at end of file
diff --git a/lib/bucky.js b/lib/bucky.js
deleted file mode 100644
index 70910ef..0000000
--- a/lib/bucky.js
+++ /dev/null
@@ -1,191 +0,0 @@
-var db = require('./db')
-var util = require('./util')
-var _ = require('lodash')
-
-var bucky = module.exports = {
-
- /* INDEX */
-
- ensureLatestThreads: function (req, res, next){
- db.getLatestThreads().then(function(threads){
- res.threads = threads
- res.threads_ids = res.threads.pluck("id").sort()
- res.keywords = _.uniq(res.threads.pluck("keyword"))
- next()
- })
- },
- ensureCommentCountsForThreads: function (req, res, next){
- db.getCommentCounts(res.threads_ids).then(function(counts){
- var lookup = {}
- counts.forEach(function(c){
- lookup[c.thread] = c
- })
- res.threads.forEach(function(thread){
- if (lookup[thread.id]) {
- thread.set("comment_count", lookup[thread.id].count)
- }
- })
- next()
- })
- },
- ensureFileCountsForThreads: function (req, res, next){
- db.getFileCounts(res.threads_ids).then(function(counts){
- var lookup = {}
- counts.forEach(function(c){
- lookup[c.thread] = c
- })
- res.threads.forEach(function(t){
- var c = lookup[t.id]
- t.set("file_count", c ? c.count : 0)
- })
- next()
- })
- },
- ensureKeywordsForThreads: function (req, res, next){
- db.getKeywords(res.keywords).then(function(keywords){
- var lookup = {}
- keywords.forEach(function(k){
- lookup[k.get('keyword')] = k
- })
- res.threads.forEach(function(t){
- var kw = t.get('keyword')
- if (! kw) return
- var k = lookup[kw]
- if (! k) return
- if (! t.get("color")) {
- t.set("color", k.get("color"))
- }
- })
- next()
- })
- },
- ensureHootbox: function (req, res, next){
- db.getCommentsForThread(1, 15, 0, "desc").then(function(hootbox){
- res.hootbox = hootbox
- next()
- })
- },
- ensureLastlog: function (req, res, next){
- db.getLastlog(6).then(function(lastlog){
- res.lastlog = lastlog
- next()
- })
- },
-
- /* DETAILS */
-
- ensureThread: function (req, res, next){
- var id = req.params.id.replace(/\D/g, "")
- if (! id) {
- return res.sendStatus(404)
- }
- db.getThread(id).then(function(thread){
- if (thread) {
- res.thread = thread
- next()
- }
- else {
- res.sendStatus(404)
- }
- })
- },
- ensureKeywordForThread: function (req, res, next){
- var keyword = res.thread.get('keyword')
- if (! keyword) return next()
- db.getKeyword(keyword).then(function(keyword){
- res.keyword = keyword
- next()
- })
- },
- ensureCommentsForThread: function (req, res, next){
- db.getCommentsForThread(res.thread.get('id')).then(function(comments){
- res.comments = comments
- next()
- })
- },
- ensureFilesForThread: function (req, res, next){
- db.getFilesForThread(res.thread.get('id')).then(function(files){
- res.files = files
- next()
- })
- },
-
- /* KEYWORDS */
-
- ensureKeyword: function (req, res, next){
- var keyword = req.params.keyword
- if (! keyword) {
- return res.sendStatus(404)
- }
- db.getKeyword(keyword).then(function(k){
- if (! k) {
- return res.sendStatus(404)
- }
- res.keyword = k
- next()
- })
- },
- ensureThreadsForKeyword: function (req, res, next){
- var keyword = req.params.keyword
- if (! keyword) {
- res.sendStatus(404)
- }
- db.getThreadsForKeyword(keyword).then(function(threads){
- res.threads = threads
- res.threads_ids = res.threads.pluck("id").sort()
- res.keywords = _.uniq(res.threads.pluck("keyword"))
- next()
- })
- },
-
- /* MAIL */
-
- ensureMailboxes: function (req, res, next){
- var username = req.user.get('username')
- var box = req.params.box
- var mbox = username + "." + box
- if (! box) {
- res.sendStatus(404)
- }
- db.getMailboxes(username).then(function(boxes){
- if (! boxes) {
- return res.sendStatus(404)
- }
- if (! boxes.models.some(function(box){ return box.get('mbox') == mbox })) {
- return res.sendStatus(404)
- }
- res.boxes = boxes
- next()
- })
- },
- ensureMailboxCounts: function (req, res, next){
- db.getMailboxCounts(res.boxes.pluck("mbox")).then(function(counts){
- var lookup = {}
- counts.forEach(function(c){
- lookup[c.mbox] = c
- })
- res.boxes.forEach(function(box){
- var count = lookup[box.get('mbox')] ? lookup[box.get('mbox')].count : 0
- box.set("count", count)
- })
- next()
- })
- },
- ensureMessages: function (req, res, next){
- db.getMessages(req.user.get('username'), req.params.box, 50, 0).then(function(messages){
- res.messages = messages
- next()
- })
- },
- ensureMessage: function(req, res, next){
- db.getMessage(req.params.id).then(function(message){
- var username = req.user.get('username')
- if (username !== message.get('recipient') && username !== message.get('sender')) {
- res.sendStatus(404)
- return
- }
- res.message = message
- next()
- })
- }
-} \ No newline at end of file
diff --git a/lib/db/bookshelf.js b/lib/db/bookshelf.js
deleted file mode 100644
index 69157cc..0000000
--- a/lib/db/bookshelf.js
+++ /dev/null
@@ -1,24 +0,0 @@
-var knex = require('knex')({
- client: 'mysql2',
- connection: {
- host : process.env.DB_HOST,
- user : process.env.DB_USER,
- password : process.env.DB_PASS,
- database : process.env.DB_NAME,
- charset : 'utf8',
- typecast : function (field, next) {
- console.log(field.type)
- if (field.type == 'BLOB') {
- return field.string()
- }
- return next()
- }
- }
-})
-
-var bookshelf = require('bookshelf')(knex)
-
-module.exports = {
- bookshelf: bookshelf,
- knex: knex,
-}
diff --git a/lib/db/index.js b/lib/db/index.js
deleted file mode 100644
index f376308..0000000
--- a/lib/db/index.js
+++ /dev/null
@@ -1,187 +0,0 @@
-var db = module.exports
-
-var connection = require("./bookshelf")
-var bookshelf = connection.bookshelf
-var knex = connection.knex
-
-
-/* MODELS */
-
-var User = db.User = bookshelf.Model.extend({
- tableName: 'users',
- hasTimestamps: false,
-})
-var Thread = db.Thread = bookshelf.Model.extend({
- tableName: 'threads',
- hasTimestamps: false,
-})
-var Comment = db.Comment = bookshelf.Model.extend({
- tableName: 'comments',
- hasTimestamps: false,
-})
-var File = db.File = bookshelf.Model.extend({
- tableName: 'files',
- hasTimestamps: false,
-})
-var Keyword = db.Keyword = bookshelf.Model.extend({
- tableName: 'keywords',
- hasTimestamps: false,
-})
-var Mailbox = db.Mailbox = bookshelf.Model.extend({
- tableName: 'boxes',
- hasTimestamps: false,
-})
-var Message = db.Message = bookshelf.Model.extend({
- tableName: 'messages',
- hasTimestamps: false,
-})
-
-/* USERS */
-
-db.createUser = function(data){
- return new db.User(data).save()
-}
-db.getUsers = function () {
- return User.query(function(qb){
- qb.orderBy("id", "desc")
- }).fetchAll()
-}
-db.getUser = function(id) {
- var model = new User({'id': id})
- return model.fetch()
-}
-db.getUserByUsername = function(username) {
- var model = new User({'username': username})
- return model.fetch()
-}
-db.getLastlog = function(limit){
- return knex.column('username').column('lastseen').select().from('users').orderBy('lastseen', 'desc').limit(limit || 10)
-}
-
-/* THREADS */
-
-db.getLatestThreads = function () {
- return Thread.query(function(qb){
- qb.orderBy("lastmodified", "desc").limit(50)
- }).fetchAll()
-}
-db.getThreadsForKeyword = function (keyword) {
- return Thread.query(function(qb){
- qb.where("keyword", "=", keyword).orderBy("id", "desc")
- }).fetchAll()
-}
-db.getThread = function (id) {
- return Thread.query("where", "id", "=", id).fetch()
-}
-db.createThread = function(data){
- return new db.Thread(data).save()
-}
-db.updateThread = function(data){
-}
-db.removeThread = function(id){
-}
-
-/* FILES */
-
-db.getFilesForThread = function (id){
- return File.query("where", "thread", "=", id).fetchAll()
-}
-db.getFileCounts = function(ids){
- return knex.column('thread').count('* as count').select().from('files').where('thread', 'in', ids).groupBy('thread')
-}
-db.getFileSizes = function(ids){
- return knex.column('thread').sum('size as size').select().from('files').where('thread', 'in', ids).groupBy('thread')
-}
-db.createFile = function(data){
- return new db.File(data).save()
-}
-db.removeFile = function(id){
-}
-
-/* COMMENTS */
-
-db.getCommentsForThread = function (id, limit, offset, order){
- order = order || "asc"
- return Comment.query(function(qb){
- qb.where("thread", "=", id).orderBy("id", order)
- if (limit) {
- qb.limit(limit)
- }
- if (offset) {
- qb.offset(offset)
- }
- }).fetchAll().then(function(comments){
- comments.forEach(function(comment){
- comment.set("comment", comment.get("comment").toString() )
- })
- return comments
- })
-}
-db.getCommentCounts = function(ids){
- return knex.column('thread').count('* as count').select().from('comments').where('thread', 'in', ids).groupBy('thread')
-}
-db.createComment = function(data){
- return new db.Comment(data).save()
-}
-db.updateComment = function(data){
-}
-db.removeComment = function(id){
-}
-
-
-/* KEYWORDS */
-
-db.getKeywords = function (keywords){
- return Keyword.query("where", "keyword", "in", keywords).fetchAll()
-}
-db.getKeyword = function (keyword) {
- return Keyword.query("where", "keyword", "=", keyword).fetch()
-}
-db.createKeyword = function(data){
- return new db.Keyword(data).save()
-}
-db.updateKeyword = function(data){
-}
-db.removeKeyword = function(id){
-}
-
-
-/* MAILBOXES */
-
-db.getMailboxes = function(username){
- return Mailbox.query("where", "owner", "=", username).fetchAll()
-}
-db.getMailboxCounts = function(boxes){
- return knex.column('mbox').count('* as count').select().from('messages').where('mbox', 'in', boxes).groupBy('mbox')
-}
-db.createMailbox = function(data){
-}
-
-/* MESSAGES */
-
-db.getMessages = function(username, box, limit, offset){
- var mbox = username + "." + box
- return Message.query(function(qb){
- qb.column('id', 'mbox', 'unread', 'sender', 'recipient', 'date', 'subject', knex.raw("CHAR_LENGTH(body) as size")).where("mbox", "=", mbox).orderBy("id", "desc")
- if (limit) {
- qb.limit(limit)
- }
- if (offset) {
- qb.offset(offset)
- }
- }).fetchAll()
-}
-db.getMessage = function (id){
- var model = new Message({'id': id})
- return model.fetch().then(function(message){
- message.set("body", message.get("body").toString() )
- return message
- })
-}
-db.createMessage = function(data){
- return new db.Message(data).save()
-}
-db.updateMessage = function(data){
-}
-db.removeMessage = function(id){
-}
diff --git a/lib/fortune.js b/lib/fortune.js
deleted file mode 100644
index 7adba5a..0000000
--- a/lib/fortune.js
+++ /dev/null
@@ -1,28 +0,0 @@
-function choice (a){ return a[ Math.floor(Math.random()*a.length) ] }
-
-var fs = require("fs"), path = require("path")
-var fortunes = {}
-var dir = "fortune"
-
-fs.readdirSync(path.resolve(dir)).forEach(function(fn){
-
- var file = dir + '/' + fn
- var stat = fs.statSync(file)
-
- if (stat && ! stat.isDirectory()) {
- fortunes[fn] = fs.readFileSync(file)
- .toString()
- .split("\n")
- .filter(function(s){ return !! s })
- }
-
-})
-
-module.exports = function(tag){
- if (tag in fortunes) {
- return choice(fortunes[tag])
- }
- else {
- return "bucky"
- }
-} \ No newline at end of file
diff --git a/lib/index.js b/lib/index.js
deleted file mode 100644
index 5aef342..0000000
--- a/lib/index.js
+++ /dev/null
@@ -1,60 +0,0 @@
-require('dotenv').load();
-var fs = require('fs')
-var app, express = require('express');
-var http = require('http');
-var https = require('https');
-var bodyParser = require('body-parser')
-var cookieParser = require('cookie-parser')
-var csurf = require('csurf')
-var path = require('path')
-var multiparty = require('multiparty')
-var ejs = require('ejs')
-var passport = require('passport')
-var sessionstore = require('sessionstore')
-var session = require('express-session')
-var multer = require('multer')
-
-var app, server
-
-var mongodb = require('mongodb')
-
-var site = module.exports = {}
-site.init = function(){
- app = express()
- app.set('port', 5000)
- app.set('view engine', 'ejs')
- app.set('views', path.join(__dirname, '../views'))
- app.use(express.static(path.join(__dirname, '../public')))
- app.use(bodyParser.json())
- app.use(bodyParser.urlencoded({ extended: false }))
- app.use( multer({ dest:'./uploads/' }).single("file") )
-
- app.use(session({
- key: 'bucky.sid',
- secret: 'argonauts',
- cookie: { domain: '.' + process.env.HOST_NAME, maxAge: 43200000000 },
- store: sessionstore.createSessionStore({
- type: 'mongodb',
- host: 'localhost',
- port: 27017,
- dbName: 'buckySessionDb',
- collectionName: 'sessions',
- timeout: 10000,
- }),
- resave: true,
- saveUninitialized: false,
- }))
- app.use(csurf({ cookie: false }))
-
- app.use(express.query())
- app.use(passport.initialize())
- app.use(passport.session())
-
- server = http.createServer(app).listen(process.env.PORT || 5000, function () {
- console.log('Bucky listening at http://5.k:%s', server.address().port)
- })
-
- site.route(app)
-}
-
-site.route = require('./router')
diff --git a/lib/middleware.js b/lib/middleware.js
deleted file mode 100644
index a744c89..0000000
--- a/lib/middleware.js
+++ /dev/null
@@ -1,23 +0,0 @@
-var middleware = module.exports = {
-
- ensureAuthenticated: function (req, res, next) {
- if (! req.isAuthenticated()) {
- req.session.returnTo = req.path
- return res.redirect('/login')
- }
- next()
- },
-
- ensureLocals: function (req, res, next) {
- res.locals.csrfToken = req.csrfToken()
- res.locals.title = "bucky"
- if (req.isAuthenticated()) {
- res.locals.show_header = true
- }
- else {
- res.locals.show_header = false
- }
- next()
- },
-
-} \ No newline at end of file
diff --git a/lib/router.js b/lib/router.js
deleted file mode 100644
index c08037e..0000000
--- a/lib/router.js
+++ /dev/null
@@ -1,169 +0,0 @@
-var auth = require('./auth')
-var middleware = require('./middleware')
-var fortune = require('./fortune')
-var bucky = require('./bucky')
-var db = require('./db')
-var util = require('./util')
-
-module.exports = function(app){
- app.all('*', middleware.ensureLocals)
-
- auth.init()
-
- app.get("/", middleware.ensureAuthenticated, function(req, res){
- res.redirect('/index')
- })
- app.get("/login", function(req, res){
- res.render("pages/login", {
- title: "login"
- })
- })
- app.get("/index", middleware.ensureAuthenticated, function(req, res){
- res.render("pages/index", {
- title: fortune("titles"),
- hoot_text: fortune("hoots"),
- })
- })
- app.get("/details/:id", middleware.ensureAuthenticated, function(req, res){
- res.render("pages/details", {})
- })
-
- app.post("/api/login", auth.loggedInLocal)
- app.get("/api/index",
- bucky.ensureLastlog,
- middleware.ensureAuthenticated,
- bucky.ensureLatestThreads,
- bucky.ensureCommentCountsForThreads,
- bucky.ensureFileCountsForThreads,
- bucky.ensureKeywordsForThreads,
- bucky.ensureHootbox,
- function(req, res){
- res.json({
- threads: res.threads,
- hootbox: res.hootbox,
- lastlog: res.lastlog,
- })
- }
- )
- app.get("/api/thread/:id",
- middleware.ensureAuthenticated,
- bucky.ensureThread,
- bucky.ensureKeywordForThread,
- bucky.ensureCommentsForThread,
- bucky.ensureFilesForThread,
- function(req, res){
- res.json({
- thread: res.thread,
- comments: res.comments,
- files: res.files,
- keyword: res.keyword,
- })
- }
- )
- app.post("/api/thread",
- middleware.ensureAuthenticated,
- function(req, res){
- // make a new thread
- })
- app.post("/api/thread/:id/comment",
- middleware.ensureAuthenticated,
- bucky.ensureThread,
- function(req, res){
- if (!req.params.id) return res.sendStatus(500)
- var comment = {
- thread: req.params.id,
- parent_id: req.body.parent_id || -1,
- username: req.user.get('username'),
- date: Math.round(+(new Date) / 1000),
- comment: req.body.comment,
- hidden: false,
- }
- db.createComment(comment).then(function(c){
- res.json(comment)
- })
- })
- app.post("/api/thread/:id/file",
- middleware.ensureAuthenticated,
- bucky.ensureThread,
- function(req, res){
- // add comments and files
- })
- app.delete("/api/thread/:id",
- middleware.ensureAuthenticated,
- function(req, res){
- // delete a thread
- })
- app.put("/api/comment/:id",
- middleware.ensureAuthenticated,
- function(req, res){
- // edit a comment
- })
- app.delete("/api/comment/:id",
- middleware.ensureAuthenticated,
- function(req, res){
- // delete a comment
- })
-
-
- app.get("/api/keyword/:keyword",
- middleware.ensureAuthenticated,
- bucky.ensureKeyword,
- bucky.ensureThreadsForKeyword,
- bucky.ensureCommentCountsForThreads,
- bucky.ensureFileCountsForThreads,
- bucky.ensureKeywordsForThreads,
- function(req, res){
- res.json({
- keyword: res.keyword,
- threads: res.threads,
- })
- }
- )
-
- app.get("/mail/",
- middleware.ensureAuthenticated,
- function(req, res){
- res.render("pages/mailbox", {title: "inbox" })
- }
- )
- app.get("/mail/:box",
- middleware.ensureAuthenticated,
- function(req, res){
- res.render("pages/mailbox", { title: util.sanitize(req.params.box) })
- }
- )
- app.get("/message/:id",
- middleware.ensureAuthenticated,
- function(req, res){
- res.render("pages/message", { title: util.sanitize(req.params.box) })
- }
- )
- app.get("/api/mailbox/:box",
- middleware.ensureAuthenticated,
- bucky.ensureMailboxes,
- bucky.ensureMailboxCounts,
- bucky.ensureMessages,
- function(req, res){
- res.json({
- user: { id: req.user.get("id"), username: req.user.get("username") },
- messages: res.messages,
- boxes: res.boxes,
- })
- }
- )
- app.get("/api/message/:id",
- middleware.ensureAuthenticated,
- bucky.ensureMessage,
- function(req, res){
- res.json({
- message: res.message,
- })
- })
- app.post("/mail/",
- middleware.ensureAuthenticated,
- function(req, res){
- // send new mail
- }
- )
-
-}
diff --git a/lib/search/index.js b/lib/search/index.js
index 8d209e6..27f436f 100644
--- a/lib/search/index.js
+++ b/lib/search/index.js
@@ -35,8 +35,16 @@ var STOPWORDS = new Set(
);
function find_term(term) {
- bdb.get(term)
-
+ var matches = bdb.get(term).split(",").map((s) => {
+ var partz = s.split(" ")
+ var match = {
+ thread: s[0],
+ comment: s[1],
+ file: s[2],
+ strength: s[3],
+ }
+ })
+ return matches
}
function search (query, start, limit) {
diff --git a/lib/search/snippet.js b/lib/search/snippet.js
new file mode 100644
index 0000000..de71911
--- /dev/null
+++ b/lib/search/snippet.js
@@ -0,0 +1,103 @@
+var util = require('../util/util')
+
+function bold_terms (s, terms) {
+
+}
+sub bold_terms
+ {
+ my ($self, $string, $terms) = @_;
+ $string = $self->strip_html($string);
+ foreach my $term (@$terms)
+ {
+ $string =~ s/\b($term)\b/<b>$1<\/b>/gi;
+ }
+ return $string;
+ }
+sub bold_snippet
+ {
+ my ($self, $string, $terms) = @_;
+ my $snippet = $self->snippet($string, $terms);
+ return $self->bold_terms($snippet, $terms);
+ }
+sub snippet
+ {
+ my ($self, $string, $terms) = @_;
+
+ # clean up the string we got
+ $string = $self->strip_html($string);
+
+ # create a regex out of the search terms
+ my $term_re = join "|", @$terms;
+
+ # take the string to be snippetized and split it into words
+ my @words = split /\s+/, $string;
+
+ # deduper for matching @words indexes, so we don't add a word twice
+ my $index_matches = {};
+
+ # words in the eventual snippet
+ my @words_matched;
+
+ # the snippet itself
+ my $snippet = '';
+
+ # counter for aggregating context after a match
+ my $aggr = 0;
+
+ # amount of context to show, in number of words surrounding a match
+ my $pad = 4;
+
+ # loop over each of the words in the string
+ for (my $i = 0; $i < scalar @words; $i++)
+ {
+ # does this word contain a match?
+ if ($words[$i] =~ /\b($term_re)\b/i && ! $self->is_stopword($1))
+ {
+ # if we aren't already aggregating, add an ellipsis
+ if (! $aggr)
+ {
+ push @words_matched, "...";
+ }
+ # look backward $pad words
+ for (my $j = -$pad; $j < 1; $j++)
+ {
+ # create a new index from the offset
+ my $idx = $i + $j;
+
+ # is this a valid index? has it already been encountered?
+ next if $idx < 0;
+ next if $idx > scalar @words;
+ next if exists $index_matches->{$i+$j};
+
+ # checks out, save this word
+ push @words_matched, $words[$i+$j];
+
+ # note the matching index in our deduper
+ $index_matches->{$i+$j} ++;
+ }
+ # enter aggregate mode -- add the next $pad words
+ $aggr = $pad;
+ }
+ # have we been told to aggregate?
+ elsif ($aggr)
+ {
+ # save this word
+ push @words_matched, $words[$i];
+
+ # add index to the deduper
+ $index_matches->{$i} ++;
+
+ # one less word to aggregate
+ $aggr--;
+ }
+ # keep snippets to a modest length
+ last if scalar @words_matched > 30;
+ }
+ # add a trailing ellipsis
+ push @words_matched, "...";
+
+ # create the snippet from the saved context words
+ $snippet = join " ", @words_matched;
+
+ return $snippet;
+ } \ No newline at end of file
diff --git a/lib/util.js b/lib/util.js
deleted file mode 100644
index e67488b..0000000
--- a/lib/util.js
+++ /dev/null
@@ -1,4 +0,0 @@
-var util = module.exports = {}
-
-util.sanitizeName = function (s){ return (s || "").replace(new RegExp("[^-_a-zA-Z0-9]", 'g'), "") }
-util.sanitize = function (s){ return (s || "").replace(/<>&/g, "") }