bean-query daemon: initial version.
This is a very basic daemon to run bean-query so that other Perl programs can call it. The speed savings is not really there yet, as to get that, it would need to leave bean-query running, perhaps timing out and reloading files as needed. That's the long term plan. Right now, all that this is useful for is to run another Perl process that wants to submit and receive answers to bean-query.
This commit is contained in:
parent
67c507367a
commit
42800427f6
3 changed files with 177 additions and 0 deletions
19
README.md
Normal file
19
README.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Beancount Tools Written in Perl
|
||||||
|
|
||||||
|
These are various tools written in Perl to interact with [Beancount project](http://furius.ca/beancount/).
|
||||||
|
|
||||||
|
## bean-query-goofy-daemon.plx
|
||||||
|
|
||||||
|
This daemon uses Perl's [`IPC::Shareable`](https://metacpan.org/pod/IPC::Shareable) library to take queries as a daemon for Beancount's `bean-query`. Note that this daemon is not particularly secure, as it has the same security flaws inherent in `IPC::Shareable` and use of named pipes (as it uses `mkfifo` from [Perl's `POSIX` library](https://perldoc.perl.org/POSIX.html) underneath.
|
||||||
|
|
||||||
|
## bean-query-daemon-lib.pl
|
||||||
|
|
||||||
|
This file defines three functions to use in client scripts that want to talk to `bean-query-goofy-daemon.plx`.
|
||||||
|
|
||||||
|
It's not an actual Perl library; just use `require` in Perl to load it.
|
||||||
|
|
||||||
|
[comment]: Local variables:
|
||||||
|
[comment]: mode: markdown
|
||||||
|
[comment]: eval: (visual-line-mode t)
|
||||||
|
[comment]: eval: (auto-fill-mode -1)
|
||||||
|
[comment]: End:
|
36
bean-query-daemon-lib.pl
Normal file
36
bean-query-daemon-lib.pl
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# License: AGPLv3-or-later
|
||||||
|
# see files COPYRIGHT, LICENSE, and AGPL-3.0.txt included in canonical repository for details.
|
||||||
|
# https://k.sfconservancy.org/NPO-Accounting/beancount-tools-in-Perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
our %BEANCOUNT_QUERY;
|
||||||
|
|
||||||
|
my $IPC_GLUE = 'BeAn';
|
||||||
|
|
||||||
|
sub BeancountQueryInitialize {
|
||||||
|
my %options = (create => 0, exclusive => 0, mode => 0600, destroy => 0);
|
||||||
|
tie %BEANCOUNT_QUERY, 'IPC::Shareable', $IPC_GLUE, { %options } or
|
||||||
|
die "BEANCOUNT_QUERY: tie failed: is the goffy beancount server running?\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub BeancountQuerySubmit($) {
|
||||||
|
my($question) = @_;
|
||||||
|
(tied %BEANCOUNT_QUERY)->shlock;
|
||||||
|
$BEANCOUNT_QUERY{fifoName} = undef;
|
||||||
|
$BEANCOUNT_QUERY{question} = $question;
|
||||||
|
(tied %BEANCOUNT_QUERY)->shunlock;
|
||||||
|
while (not defined $BEANCOUNT_QUERY{fifoName}) { sleep 1; }
|
||||||
|
die "Ceci n'est pas une pipe: BEANCOUNT_QUERY{fifoName}, $BEANCOUNT_QUERY{fifoName}:$!"
|
||||||
|
unless (-p $BEANCOUNT_QUERY{fifoName});
|
||||||
|
(tied %BEANCOUNT_QUERY)->shlock;
|
||||||
|
return $BEANCOUNT_QUERY{fifoName};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub BeancountQueryComplete {
|
||||||
|
$BEANCOUNT_QUERY{question} = undef;
|
||||||
|
(tied %BEANCOUNT_QUERY)->shunlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
122
bean-query-goofy-daemon.plx
Executable file
122
bean-query-goofy-daemon.plx
Executable file
|
@ -0,0 +1,122 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
# License: AGPLv3-or-later
|
||||||
|
# see files COPYRIGHT, LICENSE, and AGPL-3.0.txt included in canonical repository for details.
|
||||||
|
# https://k.sfconservancy.org/NPO-Accounting/beancount-tools-in-Perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use autodie qw(:all);
|
||||||
|
|
||||||
|
use Getopt::Long;
|
||||||
|
use File::Spec::Functions;
|
||||||
|
use File::Temp qw/:mktemp/;
|
||||||
|
|
||||||
|
use POSIX qw(mkfifo);
|
||||||
|
use IPC::Shareable;
|
||||||
|
|
||||||
|
my $BEANCOUNT_QUERY_CMD = "/usr/bin/bean-query";
|
||||||
|
|
||||||
|
my($VERBOSE, $BEANCOUNT_DIR, $LOAD_FILE, $REPOSITORY_URL, $FIFO_DIR) = (0, undef, undef, undef, undef);
|
||||||
|
|
||||||
|
GetOptions("verbose=i" => \$VERBOSE, "beancountDir=s" => \$BEANCOUNT_DIR,
|
||||||
|
"loadFile=s" => \$LOAD_FILE, "repositoryURL=s" => \$REPOSITORY_URL, 'fifoDir=s' => \$FIFO_DIR);
|
||||||
|
|
||||||
|
sub UsageAndExit($) {
|
||||||
|
print STDERR "usage: $0 --loadFile=/path/to/file.beancount --beancountDir=/path/to/beancountdir --fifoDir=/path/to/directory/for/fifos [ --repositoryURL=URL_OF_REPOSITORY --verbose=N ]\n";
|
||||||
|
print STDERR "\n $_[0]\n";
|
||||||
|
exit 2;
|
||||||
|
}
|
||||||
|
UsageAndExit("/path/to/direcotry/for/fifos must be provided and be an existing directory")
|
||||||
|
unless (defined $FIFO_DIR and -d $FIFO_DIR);
|
||||||
|
|
||||||
|
UsageAndExit("/path/to/file.beancount/path/to/beancountdir must be provided and be an existing directory")
|
||||||
|
unless (defined $BEANCOUNT_DIR and -d $BEANCOUNT_DIR);
|
||||||
|
chdir $BEANCOUNT_DIR;
|
||||||
|
|
||||||
|
UsageAndExit("/path/to/file.beancount must a relative path to a file that exists in $BEANCOUNT_DIR")
|
||||||
|
unless (defined $LOAD_FILE and -r $LOAD_FILE);
|
||||||
|
|
||||||
|
if (defined $REPOSITORY_URL) {
|
||||||
|
UsageAndExit("if --repositoryURL is provided, $BEANCOUNT_DIR must be a checkout of a Git repository, but there is no $BEANCOUNT_DIR/.git/config.")
|
||||||
|
unless (-r '.git/config');
|
||||||
|
|
||||||
|
open(my $gitFH, "<", ".git/config");
|
||||||
|
my $repoString;
|
||||||
|
while (my $line = <$gitFH>) {
|
||||||
|
chomp $line;
|
||||||
|
$repoString = $1
|
||||||
|
if $line =~ /^\s*url\s*=\s*(\S+)\s*$/;
|
||||||
|
}
|
||||||
|
close $gitFH;
|
||||||
|
UsageAndExit("if --repositoryURL is provided, the checkout found in $BEANCOUNT_DIR must be of that repository, but .git/config does not list a URL.") unless defined $REPOSITORY_URL;
|
||||||
|
UsageAndExit("if --repositoryURL is provided, the checkout found in $BEANCOUNT_DIR must be of that repository, but it instead appears to be a checkout of $repoString.")
|
||||||
|
unless ($repoString eq $REPOSITORY_URL);
|
||||||
|
}
|
||||||
|
my $glue = 'BeAn';
|
||||||
|
my %options = (
|
||||||
|
create => 'yes',
|
||||||
|
exclusive => 0,
|
||||||
|
mode => 0600,
|
||||||
|
destroy => 'yes',
|
||||||
|
);
|
||||||
|
my %query;
|
||||||
|
tie %query, 'IPC::Shareable', $glue, { %options } or
|
||||||
|
die "server: tie failed for $glue\n";
|
||||||
|
|
||||||
|
%query = ();
|
||||||
|
|
||||||
|
my %beancountData;
|
||||||
|
tie %beancountData, 'IPC::Shareable', $glue, { %options } or
|
||||||
|
die "server: tie failed\n";
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
if (not defined $query{question}) {
|
||||||
|
print STDERR "No question posed, sleeping 2\n" if $VERBOSE > 2;
|
||||||
|
if (defined $query{fifoName}) {
|
||||||
|
no autodie 'unlink'; unlink($query{fifoName});
|
||||||
|
$query{fifoName} = undef;
|
||||||
|
}
|
||||||
|
sleep 2;
|
||||||
|
next;
|
||||||
|
} elsif ($query{question} !~ /^[\-\@\w.\s\"\'\_\(\)]+$/) {
|
||||||
|
print STDERR "Query string $query{question} looks suspicious, not running beancount query!\n";
|
||||||
|
(tied %query)->shlock;
|
||||||
|
$query{question} = undef;
|
||||||
|
$query{fifoName} = mktemp("REJECTED_beancount-query-fifo-this-file-does-not-exist_${$}_XXXXXXXXX");
|
||||||
|
(tied %query)->shunlock;
|
||||||
|
} elsif (not defined $query{fifoName}) {
|
||||||
|
my @cmd = ($BEANCOUNT_QUERY_CMD, '-f', 'text', $LOAD_FILE, $query{question});
|
||||||
|
print STDERR "Running query: $query{question}\n" if $VERBOSE > 0;
|
||||||
|
open(my $beancountFH, "-|", @cmd);
|
||||||
|
print STDERR "Running ", join(" ", @cmd), "\n" if $VERBOSE > 1;
|
||||||
|
(tied %query)->shlock;
|
||||||
|
print STDERR "Acquired shlock on tied variable.\n" if $VERBOSE > 1;
|
||||||
|
$query{fifoName} = undef;
|
||||||
|
my $cnt = 0;
|
||||||
|
my $fifoFileName = mktemp(catfile($FIFO_DIR, "beancount-query-fifo_${$}_XXXXXXXXX"));
|
||||||
|
mkfifo($fifoFileName, 0700);
|
||||||
|
die "unable to create fifo in $fifoFileName: $!" unless -p $fifoFileName;
|
||||||
|
print STDERR "Created $fifoFileName..." if $VERBOSE > 1;
|
||||||
|
$query{fifoName} = $fifoFileName;
|
||||||
|
(tied %query)->shunlock;
|
||||||
|
open(my $fifoFH, ">", $fifoFileName);
|
||||||
|
print STDERR "and beginning write to it." if $VERBOSE > 1;
|
||||||
|
while (my $line = <$beancountFH>) {
|
||||||
|
print STDERR "." if (++$cnt % 100) and ($VERBOSE > 1);
|
||||||
|
print $fifoFH $line;
|
||||||
|
}
|
||||||
|
close $beancountFH;
|
||||||
|
close $fifoFH;
|
||||||
|
(tied %query)->shlock; $query{question} = undef; (tied %query)->shunlock;
|
||||||
|
print STDERR "...done! Data now in $fifoFileName\n" if $VERBOSE > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Local variables:
|
||||||
|
# compile-command: "perl -c bean-query-goofy-daemon.plx"
|
||||||
|
# perl-indent-level: 2
|
||||||
|
# End:
|
||||||
|
|
Loading…
Reference in a new issue