--- /dev/null
+package Gruntmaster::Data;
+use v5.14;
+use warnings;
+use parent qw/Exporter/;
+
+use JSON qw/encode_json decode_json/;
+
+use Redis;
+
+our $contest;
+my $redis = Redis->new;
+
+sub dynsub{
+ no strict 'refs';
+ *{$_[0]} = $_[1];
+}
+
+BEGIN {
+ for my $cmd (qw/multi exec smembers get hget hset sadd incr hmset/) {
+ dynsub uc $cmd, sub { say $cmd;exit;$redis->$cmd(@_) };
+ }
+}
+
+sub cp { defined $contest ? "contest.$contest." : '' }
+
+sub multi () { MULTI }
+sub rexec () { EXEC }
+
+sub problems () { SMEMBERS cp . 'problem' }
+sub contests () { SMEMBERS cp . 'contest' }
+sub jobcard () { GET cp . 'job' }
+
+sub job_results (_) { decode_json HGET cp . "job.$_[0]", 'results' }
+sub set_job_results ($+) { HSET cp . "job.$_[0]", 'results', encode_json $_[1] }
+sub problem_meta (_) { decode_json HGET cp . "pb.$_[0]", 'meta' }
+sub set_problem_meta ($+) { HSET cp . "pb.$_[0]", 'meta', encode_json $_[1] }
+
+sub defhash{
+ my ($name, @keys) = @_;
+ for my $key (@keys) {
+ dynsub "${name}_$key", sub (_) { HGET cp . "$name.$_[0]", $key };
+ dynsub "set_${name}_$key", sub ($$) { HSET cp . "$name.$_[0]", $key, $_[1] };
+ }
+
+ dynsub "insert_$name", sub {
+ my ($key, %values) = @_;
+ SADD cp . $name, $key or return;
+ HMSET cp . "$name.$key", %values;
+ };
+ dynsub "push_$name", sub {
+ my $nr = INCR cp . $name;
+ HMSET cp . "$name.$nr", @_;
+ };
+}
+
+defhash problem => qw/name level statement/;
+defhash contest => qw/start end name owner/;
+defhash job => qw/date file name private problem result result_text user/;
+
+our @EXPORT_OK = do {
+ no strict 'refs';
+ grep { $_ =~ /^[a-z]/ and exists &$_ } keys %{__PACKAGE__ . '::'};
+};
+
+1