2010-06-18 05:19:45 +02:00
|
|
|
# File: HashObject.pm
|
|
|
|
# Author: pragma_
|
|
|
|
#
|
|
|
|
# Purpose: Provides a hash-table object with an abstracted API that includes
|
|
|
|
# setting and deleting values, saving to and loading from files, etc.
|
|
|
|
|
2017-03-05 22:33:31 +01:00
|
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
2010-06-18 05:19:45 +02:00
|
|
|
package PBot::HashObject;
|
|
|
|
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
|
|
|
|
use Text::Levenshtein qw(fastdistance);
|
|
|
|
use Carp ();
|
|
|
|
|
|
|
|
sub new {
|
|
|
|
if(ref($_[1]) eq 'HASH') {
|
|
|
|
Carp::croak("Options to HashObject should be key/value pairs, not hash reference");
|
|
|
|
}
|
|
|
|
|
|
|
|
my ($class, %conf) = @_;
|
|
|
|
|
|
|
|
my $self = bless {}, $class;
|
|
|
|
$self->initialize(%conf);
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub initialize {
|
|
|
|
my ($self, %conf) = @_;
|
|
|
|
|
2014-03-05 20:30:37 +01:00
|
|
|
$self->{name} = delete $conf{name} // 'hash object';
|
|
|
|
$self->{filename} = delete $conf{filename} // Carp::carp("Missing filename to HashObject, will not be able to save to or load from file.");
|
|
|
|
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference to HashObject");
|
2010-06-18 05:19:45 +02:00
|
|
|
$self->{hash} = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub load_hash_add {
|
2010-06-23 08:51:39 +02:00
|
|
|
my ($self, $index_key, $hash, $i, $filename) = @_;
|
2010-06-18 05:19:45 +02:00
|
|
|
|
|
|
|
if(defined $hash) {
|
2010-06-23 08:51:39 +02:00
|
|
|
if(exists $self->hash->{$index_key}) {
|
2010-06-18 05:19:45 +02:00
|
|
|
if($i) {
|
2010-06-23 08:51:39 +02:00
|
|
|
Carp::croak "Duplicate hash '$index_key' found in $filename around line $i\n";
|
2010-06-18 05:19:45 +02:00
|
|
|
} else {
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach my $key (keys %$hash) {
|
2010-06-23 08:51:39 +02:00
|
|
|
$self->hash->{$index_key}{$key} = $hash->{$key};
|
2010-06-18 05:19:45 +02:00
|
|
|
}
|
2010-06-23 08:51:39 +02:00
|
|
|
return 1;
|
2010-06-18 05:19:45 +02:00
|
|
|
}
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
2017-08-06 05:14:49 +02:00
|
|
|
sub load {
|
2010-06-18 05:19:45 +02:00
|
|
|
my $self = shift;
|
|
|
|
my $filename;
|
|
|
|
|
|
|
|
if(@_) { $filename = shift; } else { $filename = $self->filename; }
|
|
|
|
|
|
|
|
if(not defined $filename) {
|
|
|
|
Carp::carp "No $self->{name} filename specified -- skipping loading from file";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-05-18 22:09:05 +02:00
|
|
|
$self->{pbot}->{logger}->log("Loading $self->{name} objects from $filename ...\n");
|
2010-06-18 05:19:45 +02:00
|
|
|
|
2010-06-18 12:46:23 +02:00
|
|
|
if(not open(FILE, "< $filename")) {
|
|
|
|
Carp::carp "Couldn't open $filename: $!\n";
|
|
|
|
Carp::carp "Skipping loading from file.\n";
|
|
|
|
return;
|
|
|
|
}
|
2010-06-18 05:19:45 +02:00
|
|
|
|
2010-06-23 08:51:39 +02:00
|
|
|
my ($hash, $index_key, $i);
|
|
|
|
$hash = {};
|
2010-06-18 05:19:45 +02:00
|
|
|
|
|
|
|
foreach my $line (<FILE>) {
|
|
|
|
$i++;
|
|
|
|
|
|
|
|
$line =~ s/^\s+//;
|
|
|
|
$line =~ s/\s+$//;
|
|
|
|
|
2010-06-23 08:51:39 +02:00
|
|
|
if($line =~ /^\[(.*)\]$/) {
|
|
|
|
$index_key = $1;
|
|
|
|
next;
|
|
|
|
}
|
2010-06-18 05:19:45 +02:00
|
|
|
|
2010-06-23 08:51:39 +02:00
|
|
|
if($line eq '') {
|
2010-06-18 05:19:45 +02:00
|
|
|
# store the old hash
|
2010-06-23 08:51:39 +02:00
|
|
|
$self->load_hash_add($index_key, $hash, $i, $filename);
|
2010-06-18 05:19:45 +02:00
|
|
|
|
|
|
|
# start a new hash
|
|
|
|
$hash = {};
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
|
|
|
my ($key, $value) = split /\:/, $line, 2;
|
|
|
|
|
2010-06-23 08:51:39 +02:00
|
|
|
if(not defined $key or not defined $value) {
|
|
|
|
Carp::croak "Error around line $i of $filename\n";
|
|
|
|
}
|
|
|
|
|
2010-06-18 05:19:45 +02:00
|
|
|
$key =~ s/^\s+//;
|
|
|
|
$key =~ s/\s+$//;
|
|
|
|
$value =~ s/^\s+//;
|
|
|
|
$value =~ s/\s+$//;
|
|
|
|
|
|
|
|
$hash->{$key} = $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
close(FILE);
|
|
|
|
|
2014-05-18 22:09:05 +02:00
|
|
|
$self->{pbot}->{logger}->log("Done.\n");
|
2010-06-18 05:19:45 +02:00
|
|
|
}
|
|
|
|
|
2017-08-06 05:14:49 +02:00
|
|
|
sub save {
|
2010-06-18 05:19:45 +02:00
|
|
|
my $self = shift;
|
|
|
|
my $filename;
|
|
|
|
|
|
|
|
if(@_) { $filename = shift; } else { $filename = $self->filename; }
|
|
|
|
|
|
|
|
if(not defined $filename) {
|
|
|
|
Carp::carp "No $self->{name} filename specified -- skipping saving to file.\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
open(FILE, "> $filename") or die "Couldn't open $filename: $!\n";
|
|
|
|
|
|
|
|
foreach my $index (sort keys %{ $self->hash }) {
|
2010-06-23 08:51:39 +02:00
|
|
|
print FILE "[$index]\n";
|
2010-06-18 05:19:45 +02:00
|
|
|
|
|
|
|
foreach my $key (sort keys %{ ${ $self->hash }{$index} }) {
|
|
|
|
print FILE "$key: ${ $self->hash }{$index}{$key}\n";
|
|
|
|
}
|
2010-06-23 08:51:39 +02:00
|
|
|
print FILE "\n";
|
2010-06-18 05:19:45 +02:00
|
|
|
}
|
|
|
|
close(FILE);
|
|
|
|
}
|
|
|
|
|
2017-08-06 05:14:49 +02:00
|
|
|
sub clear {
|
|
|
|
my $self = shift;
|
|
|
|
$self->{hash} = {};
|
|
|
|
}
|
|
|
|
|
2010-06-18 05:19:45 +02:00
|
|
|
sub find_hash {
|
2018-08-06 07:43:57 +02:00
|
|
|
my ($self, $keyword) = @_;
|
2010-06-18 05:19:45 +02:00
|
|
|
|
|
|
|
my $result = eval {
|
|
|
|
foreach my $index (keys %{ $self->hash }) {
|
2010-06-23 08:51:39 +02:00
|
|
|
if($keyword =~ m/^\Q$index\E$/i) {
|
2010-06-18 05:19:45 +02:00
|
|
|
return $index;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return undef;
|
|
|
|
};
|
|
|
|
|
|
|
|
if($@) {
|
2014-05-18 22:09:05 +02:00
|
|
|
$self->{pbot}->{logger}->log("find_hash: bad regex: $@\n");
|
2010-06-18 05:19:45 +02:00
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub levenshtein_matches {
|
|
|
|
my ($self, $keyword) = @_;
|
|
|
|
my $comma = '';
|
|
|
|
my $result = "";
|
|
|
|
|
|
|
|
foreach my $index (sort keys %{ $self->hash }) {
|
|
|
|
my $distance = fastdistance($keyword, $index);
|
|
|
|
|
|
|
|
# print "Distance $distance for $keyword (" , (length $keyword) , ") vs $index (" , length $index , ")\n";
|
|
|
|
|
|
|
|
my $length = (length($keyword) > length($index)) ? length $keyword : length $index;
|
|
|
|
|
|
|
|
# print "Percentage: ", $distance / $length, "\n";
|
|
|
|
|
2012-10-05 03:59:04 +02:00
|
|
|
if($length != 0 && $distance / $length < 0.50) {
|
2010-06-18 05:19:45 +02:00
|
|
|
$result .= $comma . $index;
|
|
|
|
$comma = ", ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$result =~ s/(.*), /$1 or /;
|
|
|
|
$result = "none" if $comma eq '';
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub set {
|
2018-08-06 07:43:57 +02:00
|
|
|
my ($self, $index, $key, $value, $dont_save) = @_;
|
2010-06-18 05:19:45 +02:00
|
|
|
|
|
|
|
my $hash_index = $self->find_hash($index);
|
|
|
|
|
|
|
|
if(not $hash_index) {
|
|
|
|
my $result = "No such $self->{name} object '$index'; similiar matches: ";
|
|
|
|
$result .= $self->levenshtein_matches($index);
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2010-06-18 06:40:12 +02:00
|
|
|
if(not defined $key) {
|
|
|
|
my $result = "[$self->{name}] $hash_index keys: ";
|
|
|
|
my $comma = '';
|
|
|
|
foreach my $k (sort keys %{ $self->hash->{$hash_index} }) {
|
|
|
|
$result .= $comma . "$k => " . $self->hash->{$hash_index}{$k};
|
2010-06-29 09:31:27 +02:00
|
|
|
$comma = "; ";
|
2010-06-18 06:40:12 +02:00
|
|
|
}
|
|
|
|
$result .= "none" if($comma eq '');
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2010-06-18 05:19:45 +02:00
|
|
|
if(not defined $value) {
|
|
|
|
$value = $self->hash->{$hash_index}{$key};
|
|
|
|
} else {
|
|
|
|
$self->hash->{$hash_index}{$key} = $value;
|
2018-08-06 07:43:57 +02:00
|
|
|
$self->save() unless $dont_save;
|
2010-06-18 05:19:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return "[$self->{name}] $hash_index: '$key' " . (defined $value ? "set to '$value'" : "is not set.");
|
|
|
|
}
|
|
|
|
|
|
|
|
sub unset {
|
|
|
|
my ($self, $index, $key) = @_;
|
|
|
|
|
|
|
|
my $hash_index = $self->find_hash($index);
|
|
|
|
|
|
|
|
if(not $hash_index) {
|
|
|
|
my $result = "No such $self->{name} object '$index'; similiar matches: ";
|
|
|
|
$result .= $self->levenshtein_matches($index);
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete $self->hash->{$hash_index}{$key};
|
2017-08-06 05:14:49 +02:00
|
|
|
$self->save();
|
2010-06-18 05:19:45 +02:00
|
|
|
|
|
|
|
return "[$self->{name}] $hash_index: '$key' unset.";
|
|
|
|
}
|
|
|
|
|
|
|
|
sub add {
|
2010-06-23 08:51:39 +02:00
|
|
|
my ($self, $index_key, $hash) = @_;
|
2010-06-18 05:19:45 +02:00
|
|
|
|
2010-06-23 08:51:39 +02:00
|
|
|
if($self->load_hash_add($index_key, $hash, 0)) {
|
2018-02-28 20:13:56 +01:00
|
|
|
$self->save();
|
2010-06-18 05:19:45 +02:00
|
|
|
} else {
|
|
|
|
return "Error occurred adding new $self->{name} object.";
|
|
|
|
}
|
|
|
|
|
2010-06-23 08:51:39 +02:00
|
|
|
return "'$index_key' added to $self->{name}.";
|
2010-06-18 05:19:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub remove {
|
|
|
|
my ($self, $index) = @_;
|
|
|
|
|
|
|
|
my $hash_index = $self->find_hash($index);
|
|
|
|
|
|
|
|
if(not $hash_index) {
|
|
|
|
my $result = "No such $self->{name} object '$index'; similiar matches: ";
|
|
|
|
$result .= $self->levenshtein_matches($index);
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete $self->hash->{$hash_index};
|
2017-08-06 05:14:49 +02:00
|
|
|
$self->save();
|
2010-06-18 05:19:45 +02:00
|
|
|
|
|
|
|
return "'$hash_index' removed from $self->{name}.";
|
|
|
|
}
|
|
|
|
|
|
|
|
# Getters and setters
|
|
|
|
|
|
|
|
sub hash {
|
|
|
|
my $self = shift;
|
|
|
|
return $self->{hash};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub filename {
|
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
if(@_) { $self->{filename} = shift; }
|
|
|
|
return $self->{filename};
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|