aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Lemon <chaffinach+git@protonmail.ch>2022-04-22 14:54:33 +0100
committerMatthew Lemon <chaffinach+git@protonmail.ch>2022-04-22 14:54:33 +0100
commitf613b41f5d46563458386d22f2d484c8f90f1333 (patch)
treec7475aeecc06c5a2bc9b37e578614a86a2d91455
parentff837c754444d5be95d2b667510ffa78eace2d41 (diff)
parent3b608c886dd630b7268b286c8b2ec493f66fdf9e (diff)
update
Diffstat (limited to '')
-rw-r--r--.gitignore1
-rw-r--r--ledgerscripts/categories.json653
-rw-r--r--ledgerscripts/csv_processor147
-rw-r--r--ssh.pl3
-rwxr-xr-xtw_hooks/on-add_scheduled_work_task.pl305
-rw-r--r--tw_hooks/simple_test.t2
6 files changed, 1060 insertions, 51 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..87697ca
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+TransHist.csv
diff --git a/ledgerscripts/categories.json b/ledgerscripts/categories.json
new file mode 100644
index 0000000..67aab26
--- /dev/null
+++ b/ledgerscripts/categories.json
@@ -0,0 +1,653 @@
+{
+ "data": [
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "CASH RB SCOT JUN06 TESCO BRWCK @14:07 ATM"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "CASH RB SCOT JUN06 TESCO BRWCK @14:05 ATM"
+ },
+ {
+ "category": "expenses:groceries",
+ "desc": "WM MORRISONS STORE BERWICK UT )))"
+ },
+ {
+ "category": "expenses:streaming",
+ "desc": "SPOTIFY LONDON VIS"
+ },
+ {
+ "desc": "ROYAL MAIL GROUP L CHESTERFIELD VIS",
+ "category": "expenses:postage"
+ },
+ {
+ "desc": "W M MORRISON PETRO BERWICK UPON VIS",
+ "category": "expenses:car:petrol"
+ },
+ {
+ "desc": "ZURICH INSURANCE DD",
+ "category": "expenses:insurance"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "INT'L 0034245158 AMAZON PRIME*2T7IR AMZN.CO.UK/PM VIS"
+ },
+ {
+ "desc": "PAYPAL *JOULES PLC 35314369001 VIS",
+ "category": "expenses:clothing"
+ },
+ {
+ "desc": "MARKS&SPENCER PLC INTERNET VIS",
+ "category": "expenses:clothing"
+ },
+ {
+ "category": "expenses:clothing",
+ "desc": "NEXT DIRECTORY ONLINE VIS"
+ },
+ {
+ "desc": "INT'L 0026603799 ETSY ETSY.COM VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:fees",
+ "desc": "Non-Sterling Transaction Fee DR"
+ },
+ {
+ "category": "expenses:hosting",
+ "desc": "INT'L 0026586608 DIGITALOCEAN.COM DIGITALOCEAN. USD 38.04 @ 1.4146 Visa Rate VIS"
+ },
+ {
+ "desc": "REFRESHCARTRIDGES. INTERNET VIS",
+ "category": "expenses:stationery"
+ },
+ {
+ "category": "expenses:hotels",
+ "desc": "PREMIER INN4403326 HULL VIS"
+ },
+ {
+ "category": "expenses:harvey_ctf",
+ "desc": "HARVEY W LEMON CTF PP382029B SO"
+ },
+ {
+ "category": "expenses:utilities:gas_and_electric",
+ "desc": "SCOTTISHPOWER DD"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "INT'L 0018811184 AMAZON.CO.UK*2T6ZK AMAZON.CO.UK VIS"
+ },
+ {
+ "desc": "PAYPAL *GAILAUK 35314369001 VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "B&M 013 - BERWICK BERWICK-UPON- )))",
+ "category": "expenses:household_goods"
+ },
+ {
+ "desc": "TESCO STORES 6181 BERWICK )))",
+ "category": "expenses:groceries"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "GO OUTDOORS BERWICK VIS"
+ },
+ {
+ "category": "expenses:parking",
+ "desc": "Q PARK OMNI EDINBURGH )))"
+ },
+ {
+ "desc": "WATERSTONES EDINBURGH )))",
+ "category": "expenses:books"
+ },
+ {
+ "desc": "SUMUP *MARSHALL C BERWICK-UPON- )))",
+ "category": "expenses:cafe"
+ },
+ {
+ "desc": "HARVEY NICHOLS EDINBURGH VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "PIZZA EXPRESS EDINBURGH 185 VIS",
+ "category": "expenses:restaurant"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "AMZNMKTPLACE AMAZO AMAZON.CO.UK VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "Amazon.co.uk*2T9UI AMAZON.CO.UK VIS"
+ },
+ {
+ "desc": "NORTHUMBRIAN WATER DD",
+ "category": "expenses:utilities:water"
+ },
+ {
+ "category": "expenses:utilities:tv_licence",
+ "desc": "TV LICENCE MBP DD"
+ },
+ {
+ "desc": "NCC - RECEIPTS ACC DD",
+ "category": "expenses:council_tax"
+ },
+ {
+ "desc": "SANTANDER MORTGAGE DD",
+ "category": "expenses:mortgage"
+ },
+ {
+ "desc": "ROYAL LONDON DD",
+ "category": "expenses:insurance"
+ },
+ {
+ "desc": "SCHOOLGRID LTD DD",
+ "category": "expenses:school"
+ },
+ {
+ "desc": "PNET1056659-3 DD",
+ "category": "expenses:internet"
+ },
+ {
+ "desc": "400713 72004526 INTERNET TRANSFER TFR",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "TARASOVA E MAY TEACHING CR",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "INT'L 0088268920 Kindle Svcs*2T3WD9 353-12477661 VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "Marquee Arts Limit INVOICE 2 CR",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "DFT MAIN CR",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "HMRC CHILD BENEFIT CR"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "INT'L 0080951970 Etsy.com - Marvlin Dublin VIS"
+ },
+ {
+ "desc": "INT'L 0080935604 PAYPAL *STORYBUNDL 402-935-7733 VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "INT'L 0080935603 Kindle Svcs*2T1GK6 353-12477661 VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "INT'L 0080951971 ETSY ETSY.COM VIS"
+ },
+ {
+ "desc": "G C GRIEVE BERWICK UPON )))",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:takeaway",
+ "desc": "GREGGS BERWICK UPON )))"
+ },
+ {
+ "desc": "ROBERTSONS BERWICK-UPON- VIS",
+ "category": "expenses:clothing"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "TARASOVA E EXPENSE MAZE KIDS CR"
+ },
+ {
+ "desc": "INT'L 0067376845 Etsy.com - ForgetM Dublin VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "GOOGLE *Google Sto g.co/helppay# VIS"
+ },
+ {
+ "desc": "WH SMITH BERWICK-O-TWE )))",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:cafe",
+ "desc": "SUMUP *DEYNS DELI BERWICK UPON )))"
+ },
+ {
+ "desc": "VODAFONE BERWICK UPON )))",
+ "category": "expenses:phone"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "Amazon.co.uk*2T7L8 AMAZON.CO.UK VIS"
+ },
+ {
+ "category": "expenses:sophie_isa",
+ "desc": "FAMILY EQUITY PLAN DD"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "AMZNMktplace amazon.co.uk VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "Amazon.co.uk*MK1VD AMAZON.CO.UK VIS"
+ },
+ {
+ "desc": "INT'L 0013369091 Kindle Svcs*MK27G3 353-12477661 VIS",
+ "category": "expenses:books"
+ },
+ {
+ "desc": "INT'L 0006888197 PP*ROBLOX CORP ROB 402-935-7733 VIS",
+ "category": "expenses:games"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "INT'L 0099818323 AMAZON.CO.UK*MK3DJ AMAZON.CO.UK VIS"
+ },
+ {
+ "desc": "INT'L 0013387518 AMAZON.CO.UK*MK7AM AMAZON.CO.UK VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "INT'L 0099837342 Amazon.co.uk*MK55S AMAZON.CO.UK VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "AMZNMktplace amazon.co.uk VIS"
+ },
+ {
+ "desc": "COLDINGHAM BEACH C EYEMOUTH )))",
+ "category": "expenses:cafe"
+ },
+ {
+ "category": "expenses:pharmacueticals",
+ "desc": "LLOYDS PHARMACY BERWICK UPON )))"
+ },
+ {
+ "category": "expenses:gifts",
+ "desc": "CARD FACTORY BERWICK )))"
+ },
+ {
+ "desc": "THE WORKS BERWICK UPON )))",
+ "category": "expenses:stationery"
+ },
+ {
+ "desc": "AMZNMktplace amazon.co.uk VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "AMZNMktplace amazon.co.uk VIS"
+ },
+ {
+ "category": "expenses:clothing",
+ "desc": "SPORTSDIRECT 410 08443325410 VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "HMRC CHILD BENEFIT CR"
+ },
+ {
+ "desc": "INT'L 0092773407 AMZNFreeTime 353-12477661 VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:streaming",
+ "desc": "NETFLIX.COM 18665797172 VIS"
+ },
+ {
+ "desc": "IZ *Ashleigh Affle Ayton VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "PAYPAL*LEMON MATTH Richmond VIS"
+ },
+ {
+ "desc": "GOCARDLESS FIRST PAYMENT DD",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "INT'L 0086023680 Amazon.co.uk*MK1ZN AMAZON.CO.UK VIS"
+ },
+ {
+ "desc": "SAVERS HEALTH & BE BERWICK-UPON- )))",
+ "category": "expenses:pharmacueticals"
+ },
+ {
+ "category": "expenses:postage",
+ "desc": "POST OFFICE COUNTE BERWICK UPON VIS"
+ },
+ {
+ "category": "expenses:clothing",
+ "desc": "CLARKS.CO.UK STREET VIS"
+ },
+ {
+ "desc": "PLUSNET PLC PAY AC DD",
+ "category": "expenses:phone"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "INT'L 0060498418 AMZN DIGITAL*MK8WO 35312477661 VIS"
+ },
+ {
+ "desc": "INT'L 0053560906 Amazon.co.uk*MK0RW AMAZON.CO.UK VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "HOME BARGAINS BERW BERWICK )))",
+ "category": "expenses:household_goods"
+ },
+ {
+ "desc": "AMZNMktplace amazon.co.uk VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "INT'L 0045993827 Amazon.co.uk*MK1C8 AMAZON.CO.UK VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "INT'L 0045993826 AMAZON.CO.UK*MK2GI AMAZON.CO.UK VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "AMZNMktplace amazon.co.uk VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "PLUSNET PLC PAY AC DD",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "HLAM REGULAR SAVIN DD",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "AMZNMKTPLACE AMAZO AMAZON.CO.UK VIS"
+ },
+ {
+ "desc": "INT'L 0032865732 STEAMGAMES.COM 425 Hamburg VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "INT'L 0020100138 Amazon Prime*MK5ZD amzn.co.uk/pm VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "INT'L 0013564742 ETSY ETSY.COM VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "Non-Sterling Transaction Fee DR"
+ },
+ {
+ "desc": "INT'L 0013541835 HOVER 8667316556 USD 20.17 @ 1.3796 Visa Rate VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "Non-Sterling Transaction Fee DR"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "INT'L 0013541834 DIGITALOCEAN.COM DIGITALOCEAN. USD 38.04 @ 1.3797 Visa Rate VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "Spotify P14A7F5C7D London VIS"
+ },
+ {
+ "desc": "THE ROYAL GARDEN B BERWICK UPON )))",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "W M MORRISON PETRO BERWICK 168 VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "HARVEY W LEMON CTF PP382029B SO",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "NORTHUMBRIAN WATER DD",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "TV LICENCE MBP DD"
+ },
+ {
+ "desc": "NCC - RECEIPTS ACC DD",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "SCOTTISHPOWER DD"
+ },
+ {
+ "desc": "SANTANDER MORTGAGE DD",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "ROYAL LONDON DD"
+ },
+ {
+ "desc": "SCHOOLGRID LTD DD",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "SCHOOLGRID LTD DD"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "PNET1056659-3 DD"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "DFT MAIN CR"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "HMRC CHILD BENEFIT CR"
+ },
+ {
+ "desc": "400713 72004526 INTERNET TRANSFER TFR",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "TARASOVA E APRIL TEACHING CR",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "INT'L 0057478724 AMAZON.CO.UK*M48OX AMAZON.CO.UK VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "NORTHUMBERLAND COU MORPETH )))"
+ },
+ {
+ "desc": "BRITISH BEEF JERKY MORPETH )))",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "Marquee Arts Limit INVOICE 1 CR",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "Non-Sterling Transaction Fee DR"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "INT'L 0050174075 DEREK SIVERS HTTPSSIVE.RS USD 15.00 @ 1.3888 Visa Rate VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "AMZNMKTPLACE AMAZO AMAZON.CO.UK VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "GALEDIN VETS GLN B BERWICK-UPON- VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "FAMILY EQUITY PLAN DD"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "INT'L 0043563022 Etsy.com - victori Dublin VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "Non-Sterling Transaction Fee DR"
+ },
+ {
+ "desc": "INT'L 0037208809 Fastmail Pty Ltd Melbourne USD 6.00 @ 1.3729 Visa Rate VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "CARD FACTORY BERWICK )))"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "THE BAG N BOX MAN 01295 788522 VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "SECUREPAY.MBNA.CO. CHESTER VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "WWW.APLPACKAGING.C WORTHING VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "ROYAL MAIL GROUP L CHESTERFIELD VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "TARASOVA E EXPENSES CR"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "INT'L 0024393137 AMAZON.CO.UK*M426C AMAZON.CO.UK VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "ROYAL MAIL GROUP L CHESTERFIELD VIS"
+ },
+ {
+ "category": "expenses:clothing",
+ "desc": "J P Boden and Co L London VIS"
+ },
+ {
+ "category": "expenses:UNKNOWN",
+ "desc": "Etsy Ireland Unlim /PAYER ACC/POC2MZY CR"
+ },
+ {
+ "desc": "NORTHRIDGE FINANCE DD",
+ "category": "expenses:car"
+ },
+ {
+ "desc": "HMRC CHILD BENEFIT CR",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "FERRIBY SERVICE ST HULL )))",
+ "category": "expenses:takeaway"
+ },
+ {
+ "desc": "INT'L 0057536052 EDINBURGH WAVERLEY EDINBURGH )))",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "SPORTSDIRECT 410 08443325410 )))",
+ "category": "expenses:clothing"
+ },
+ {
+ "desc": "BARKERS COTTINGHAM )))",
+ "category": "expenses:takeaway"
+ },
+ {
+ "desc": "PIZZA EXPRESS BEVERLEY 1921 VIS",
+ "category": "expenses:restaurant"
+ },
+ {
+ "desc": "INT'L 0006888196 Kindle Svcs*MK3IH9 353-12477661 VIS",
+ "category": "expenses:books"
+ },
+ {
+ "desc": "MARKS&SPENCER PLC BERWICK UPON )))",
+ "category": "expenses:groceries"
+ },
+ {
+ "desc": "COOPLANDS BAKERY C COTTINGHAM HU )))",
+ "category": "expenses:groceries"
+ },
+ {
+ "desc": "PAYPAL *NEXT RETAI 35314369001 VIS",
+ "category": "expenses:groceries"
+ },
+ {
+ "desc": "THE DEEP 01482 381000 )))",
+ "category": "expenses:days_out"
+ },
+ {
+ "desc": "W H SMITH BEVERLEY )))",
+ "category": "expenses:books"
+ },
+ {
+ "desc": "East Riding Parkin East Riding o VIS",
+ "category": "expenses:parking"
+ },
+ {
+ "desc": "KINGS HEAD BEVERLEY )))",
+ "category": "expenses:pub"
+ },
+ {
+ "desc": "TESCO STORES 6181 BERWICK VIS",
+ "category": "expenses:groceries"
+ },
+ {
+ "desc": "BAKERS DOZEN COTTINGHAM )))",
+ "category": "expenses:takeaway"
+ },
+ {
+ "desc": "TESCO STORE 2607 HADDINGTON )))",
+ "category": "expenses:groceries"
+ },
+ {
+ "desc": "Amazon.co.uk*2T3NA AMAZON.CO.UK VIS",
+ "category": "expenses:UNKNOWN"
+ },
+ {
+ "desc": "LNER WEB SALES 07500447482 VIS",
+ "category": "expenses:train"
+ },
+ {
+ "desc": "WETHERBY WH SMITHS WEST YORKSHIR )))",
+ "category": "expenses:stationery"
+ },
+ {
+ "desc": "NCC PAYMENTS ACCOU CR",
+ "category": "expenses:stationery"
+ }
+
+ ]
+} \ No newline at end of file
diff --git a/ledgerscripts/csv_processor b/ledgerscripts/csv_processor
new file mode 100644
index 0000000..f78d1d0
--- /dev/null
+++ b/ledgerscripts/csv_processor
@@ -0,0 +1,147 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use 5.010;
+
+use Text::CSV;
+use JSON;
+
+my $csv = Text::CSV->new(
+ { sep_char => ',',
+ binary => 1,
+ quote => "\N{FULLWIDTH QUOTATION MARK}"
+ }
+);
+
+my %transaction;
+my @jlist; # used to create the categories json fil0
+my $cat_json;
+my @uncategorised;
+
+# given a list, make it unique
+# this works because a hash cannot have duplicate keys
+# so you can keep adding keys to a temp_hash with an
+# arbitary value (in this case 0), then extract the keys
+# from the hash at the end - they will all have to be unique
+sub uniq {
+ my %temp_hash = map { $_, 0 } @_;
+ return keys %temp_hash;
+}
+
+my $file = $ARGV[0] or die "Need to get CSV file on the command line\n";
+open( my $csvdata, '<:encoding(UTF-8)', $file )
+ or die "Could not open '$file' $!\n";
+
+{
+ open( my $category_file, '<', "categories.json" )
+ or die "Could not open category file $!\n";
+ local $/ = undef; # slurp mode!
+ $cat_json = <$category_file>;
+ close($category_file);
+}
+
+my $catref = decode_json $cat_json;
+my $cats = $catref->{"data"};
+
+# say qq($catref_d->[0]->{"desc"});
+
+my @descs = map $_->{"desc"}, @{$cats};
+
+# self-explanatory
+sub get_category_from_desc {
+ my $desc = shift;
+ for my $hsh ( @{$cats} ) {
+ if ( $desc eq $hsh->{"desc"} ) {
+ return $hsh->{"category"};
+ }
+ }
+ return "UNKNOWN -> $desc";
+}
+
+while ( my $line = <$csvdata> ) {
+ # remove the BOM from first line
+ $line =~ s/^\N{BOM}//;
+ # TODO working on this
+ # $line =~ s/.*"(\d+),(\d+\.\d+)"/.*"$1$2"/;
+ chomp $line;
+ if ( $csv->parse($line) ) {
+ my @fields = $csv->fields();
+
+ # parse the date
+ # everything ends up in the transaction hash
+ $transaction{day} = substr $fields[0], 0, 2;
+ $transaction{month} = substr $fields[0], 3, 2;
+ $transaction{year} = substr $fields[0], 6, 4;
+ $transaction{date} = $fields[0];
+
+ # default expense type
+ $transaction{exp_type} = "expenses:UNKNOWN";
+
+ # remove extraneous spaces from description
+ $fields[1] =~ s/\s+/ /g;
+
+ # used to create the categories json file - see below
+ push @jlist, { "desc" => $fields[1], "category" => "expenses:UKNOWN" };
+
+ # add the description and cost
+ $transaction{desc} = $fields[1];
+ $transaction{cost} = $fields[2];
+
+ my $c = get_category_from_desc $transaction{desc};
+
+ if ( $c =~ /^UNKNOWN ->.*$/ ) {
+ push @uncategorised, $transaction{desc};
+ }
+ else { $transaction{exp_type} = $c }
+
+ # parse the transaction type. Unused in ledger journal at moment
+ if ( $fields[1] =~ /^.+(VIS|DR|DD|TFR|CR|SO|ATM|\)\)\))$/ ) {
+ $transaction{type} = $1;
+ }
+ else { die("CANNOT DETERMINE TYPE!\n") }
+
+ # if the cost is negative, it is an expense category
+ if ( $fields[2] =~ /^\-/ ) {
+ $transaction{expense} = 1;
+ }
+ else { $transaction{expense} = 0; }
+
+ # write out the three line block representing the transaction
+ # in the ledger journal file
+ print join "",
+ (
+ $transaction{year}, "/", $transaction{month}, "/",
+ $transaction{day}, " ", "*", " ",
+ $transaction{desc}
+ ),
+ "\n";
+
+ if ( $transaction{expense} == 1 ) {
+ ( my $cost = $transaction{cost} ) =~ s/^\-//;
+ chomp $cost;
+ print qq(\t$transaction{exp_type}\t$cost\n);
+ print "\tassets:hsbc current\t$transaction{cost}\n";
+ print "\n";
+ }
+ else {
+ print "\tincome:UNKNOWN\t-$transaction{cost}\n";
+ print "\tassets:hsbc current\t$transaction{cost}\n";
+ print "\n";
+ }
+ }
+ else { warn "Line could not be parsed: $line\n"; }
+}
+
+say "Unrecognized payees that need to be added to categories.json:";
+
+for ( uniq @uncategorised ) { say "* $_" }
+
+# The following code is used to output a JSON file
+# to be used for categories. Uncomment for use.
+# my $data = encode_json {data => \@jlist};
+
+# open(my $fh, '>', "/tmp/categories.json") or die "Could not open file '/tmp/toss.json' $!";
+# print $fh $data;
+# close $fh;
+# print "done\n";
diff --git a/ssh.pl b/ssh.pl
index d22d74d..a846637 100644
--- a/ssh.pl
+++ b/ssh.pl
@@ -3,7 +3,8 @@ use warnings;
use Net::OpenSSH;
use JSON;
-my $host = "192.168.122.184";
+my $host = "10.13.37.203";
+# my $host = "192.168.122.184";
my $user = "lemon";
my $ssh = Net::OpenSSH->new($host, user => $user);
diff --git a/tw_hooks/on-add_scheduled_work_task.pl b/tw_hooks/on-add_scheduled_work_task.pl
index 1ac836c..44999b7 100755
--- a/tw_hooks/on-add_scheduled_work_task.pl
+++ b/tw_hooks/on-add_scheduled_work_task.pl
@@ -1,4 +1,4 @@
-#!/bin/env perl
+#!/usr/bin/perl
use warnings;
use strict;
@@ -10,96 +10,303 @@ use Net::OpenSSH;
my @short_months = qw(January February March April May June July August September October November December);
+#my @short_months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
# subs
-sub parse_scheduled
-{
+sub parse_scheduled {
my $sched_date = shift;
return DateTime::Format::ISO8601->parse_datetime($sched_date);
}
-
-# ALGORITHM
-# Parse the scheduled attribute from TW
my %token_regexes = (
- tdelta => qr/\+(\d+)/, # +INT (see remind man page)
- trepeat => qr/\*(\d+)/, # *INT (see remind man page)
+ tdelta => qr/\+(\d+)/, # +INT (see remind man page)
+ trepeat => qr/\*(\d+)/, # *INT (see remind man page)
+ delta => qr/D\+(\d+)/,
+ repeat => qr/D\*(\d+)/,
);
-
-
-my $added_task = <STDIN>;
-my $work_rem_file = '~/.reminders/work.rem';
-my $decoded_task = decode_json $added_task;
+my $added_task = <STDIN>;
+my $work_rem_file = '~/.reminders/work.rem';
+my $decoded_task = decode_json $added_task;
my $original_description = ${$decoded_task}{description};
my $tdelta;
my $trepeat;
+my $delta;
+my $repeat;
+
+if ( ( $original_description =~ m/$token_regexes{delta}/g ) ) {
+ $delta = "+$1";
+ $original_description =~
+ s/$token_regexes{delta}//g; # remove the delta token
+}
+else {
+ $delta = "";
+}
+
+if ( ( $original_description =~ m/$token_regexes{repeat}/g ) ) {
+ $repeat = "*$1";
+ $original_description =~
+ s/$token_regexes{repeat}//g; # remove the repeat token
+}
+else {
+ $repeat = "";
+}
-if (($original_description =~ m/$token_regexes{tdelta}/g)) {
- $tdelta = "+$1";
- $original_description =~ s/$token_regexes{tdelta}//g; # remove the delta time token
-} else {
+if ( ( $original_description =~ m/$token_regexes{tdelta}/g ) ) {
+ $tdelta = "+$1"
+ ; # corresponds to tdelta in remind: how many minutes prior to reminder it reminds
+ $original_description =~
+ s/$token_regexes{tdelta}//g; # remove the delta time token
+}
+else {
$tdelta = "";
-};
+}
-if (($original_description =~ m/$token_regexes{trepeat}/g)) {
- if ($tdelta eq "") { die "Cannot have a repeat token without a delta token" };
- $trepeat = "*$1";
- $original_description =~ s/$token_regexes{trepeat}//g; # remove the delta time token
-} else {
+if ( ( $original_description =~ m/$token_regexes{trepeat}/g ) ) {
+ if ( $tdelta eq "" ) {
+ die "Cannot have a repeat token without a delta token";
+ }
+ $trepeat = "*$1"
+ ; # corresponds to trepeat in remind: how many minutes within tdelta it pings repeatedly
+ $original_description =~
+ s/$token_regexes{trepeat}//g; # remove the delta time token
+}
+else {
$trepeat = "";
-};
+}
-my $tags = ${$decoded_task}{tags}; # alternative - not using -> in the ref
+my $tags = ${$decoded_task}{tags}; # alternative - not using -> in the ref
my $scheduled_dt;
-if ($decoded_task->{scheduled} and (scalar grep {$_ eq "dft" } @{$tags})) {
+if ( $decoded_task->{scheduled} and ( scalar grep { $_ eq "dft" } @{$tags} ) ) {
$scheduled_dt = parse_scheduled $decoded_task->{scheduled};
- my $date = $scheduled_dt->day();
- my $month = $short_months[$scheduled_dt->month()-1];
- my $year = $scheduled_dt->year();
- my $hr = $scheduled_dt->hour();
- my $min = $scheduled_dt->minute();
- my $time = $scheduled_dt->hms();
- # Convert it into Remind format
- my $remind_line = "REM $date $month $year AT $time $tdelta $trepeat MSG $original_description \%b\n";
+ # my @test_task = `task add Bobbins from Perl`;
+ my $port = 22;
+ my $date = $scheduled_dt->day();
+ my $month = $short_months[ $scheduled_dt->month() - 1 ];
+ my $year = $scheduled_dt->year();
+ my $hr = $scheduled_dt->hour();
+ my $min = $scheduled_dt->minute();
+ my $time = substr $scheduled_dt->hms(), 0,
+ 5; # we do not want seconds in the time format
+ # Convert it into Remind format with %" bits that mean you don't get the
+ # shit in wyrd
+
+ $original_description =~ s/\s+$//; # trim white space from end of string
+ my $remind_line =
+"REM $date $month $year $delta $repeat AT $time $tdelta $trepeat MSG \%\"$original_description\%\" \%b\n";
$remind_line =~ s/ +/ /g;
-
+
# Log into remote server
-
- my $host = $ENV{"TW_HOOK_REMIND_REMOTE_HOST"} or die "Cannot get TW_HOOK_REMIND_REMOTE_HOST environment variable";
- my $user = $ENV{"TW_HOOK_REMIND_REMOTE_USER"} or die "Cannot get TW_HOOK_REMIND_REMOTE_USER environment variable";
+ my $host = $ENV{"TW_HOOK_REMIND_REMOTE_HOST"}
+ or die "Cannot get TW_HOOK_REMIND_REMOTE_HOST environment variable";
+ my $user = $ENV{"TW_HOOK_REMIND_REMOTE_USER"}
+ or die "Cannot get TW_HOOK_REMIND_REMOTE_USER environment variable";
+
+ # use correct port
+ if ( $host =~ m/.*\.xyz$/ ) { $port = 2222 }
- my $ssh = Net::OpenSSH->new($host, user => $user);
+ say "Trying to establish connection at $host:$port ...";
+ my $ssh = Net::OpenSSH->new( $host, user => $user, port => $port );
$ssh->error and die "Couldn't establish SSH connection: " . $ssh->error;
- # Check for presece or remind file
- if ($ssh->test("ls $work_rem_file") != 1) { die "Cannot find $work_rem_file on $host."};
+ # Check for presence or remind file
+ if ( $ssh->test("ls $work_rem_file") != 1 ) {
+ die "Cannot find $work_rem_file on $host.";
+ }
# If it is there, back it up
- $ssh->system("cp $work_rem_file $work_rem_file.bak");
+ $ssh->system("cp $work_rem_file $work_rem_file.bak")
+ or die "Cannot create a back-up of remind file.";
# Append the Remind formatted line to the original remind file
- $ssh->system({stdin_data => $remind_line}, "cat >> $work_rem_file") or die "Cannot append text: " . $ssh->error;
+ $ssh->system( { stdin_data => $remind_line }, "cat >> $work_rem_file" )
+ or die "Cannot append text: " . $ssh->error;
# Get content of remind file
my @out_file = $ssh->capture("cat $work_rem_file");
-
print qq/
-Contents of $work_rem_file on $host is now:\n/,
- @out_file;
+Contents of $work_rem_file on $host is now:\n/, @out_file;
# TODO - we need to strip away the %:MIN syntax from the original
# description - need to substitute it here!
$decoded_task->{description} = $original_description;
print encode_json $decoded_task;
exit 0;
-} else {
- print encode_json $decoded_task;
- exit 0;
}
+else {
+ print $added_task;
+ print("Add hook not used.\n");
+ exit 0;
+}
+
+=pod
+
+=head1 NAME
+
+on-add_scheduled_work_task
+
+=head1 SYNOPSIS
+
+=over
+
+=item C<task add Meaningless event at work +dft scheduled:2021-10-10>
+
+This will create an untimed reminder for 10 October 2021.
+
+=item C<task add Meaningless event at work D+2 +dft scheduled:2021-10-10>
+
+This will create an untimed reminder for 10 October 2021 and remind you of it 2 days in advance.
+
+=item C<task add Meaningless event at work D*2 +dft scheduled:2021-10-10>
+
+This will create an untimed reminder for 10 October 2021 and every other day subsequently.
+
+=item C<task add Meaningless event at work D*2 +dft scheduled:2021-10-10T10:00Z>
+
+This will create a reminder for 10 October 2021 at 11:00BST and every other day subsequently at the same time.
+
+=item C<task add Meaningless meeting at work +dft scheduled:2021-10-10T10:00Z>
+
+This will create a reminder for 11:00BST for 10 October 2021.
+
+=item C<task add Meaningless meeting at work +10 +dft scheduled:2021-10-10T10:00Z>
+
+This will create a reminder for 11:00BST for 10 October 2021, and hassle you once 10 minutes before the meeting.
+
+=item C<task add Meaningless meeting at work +10 *1 +dft scheduled:2021-10-10T10:00Z>
+
+This will create a reminder for 11:00BST for 10 October 2021, and hassle you once 10 minutes before the meeting AND each minute
+from then until the start of the meeting.
+
+=back
+
+=head1 DESCRIPTION
+
+This is a Taskwarrior hook for interacting with the remind calendar on a remote server. It currently only
+works under a specific set of circumstances which will be explained here.
+
+The current implementation will add a remind item for a taskwarrior item which has the tag "dft" and is "scheduled"
+for a time and date.
+
+=head1 PREREQUISITES
+
+=over
+
+=item * A remote server and its IP address or domain name with remind already set up, and ssh access to it.
+
+=item * Taskwarrior - with this perl script at ~/.task/hooks/on-add_scheduled_work_task.pl
+
+=item * An environment variable TW_HOOK_REMIND_REMOTE_HOST set with the IP address or domain name of the remote server which hosts remind.
+
+=item * An environment variable TW_HOOK_REMIND_REMOTE_USER set with the username on the remote server which ssh requires to log in.
+
+=item * The following perl dependences: JSON, Net::OpenSSH, DateTime and DateTime::Format::ISO8601 installed.
+
+=back
+
+=head1 REMIND SYNTAX
+
+C<REM [ONCE] [date_spec] [back] [delta] [repeat] [PRIORITY prio] [SKIP | BEFORE | AFTER] [OMIT omit_list] [OMITFUNC omit_function] [AT time
+[tdelta] [trepeat]] [SCHED sched_function] [WARN warn_function] [UNTIL expiry_date] [SCANFROM scan_date | FROM start_date] [DURATION
+duration] [TAG tag] E<lt>MSG | MSF | RUN | CAL | SATISFY | SPECIAL special | PS | PSFILEE<gt> body>
+
+The elements we are interested in are:
+
+=over
+
+=item * delta (for advanced warning of the date)
+
+=item * repeat (for repeating from the trigger date)
+
+=item * tdelta (for advanced warning of the AT time)
+
+=item * trepeat (for repeating the advanced reminder)
+
+=back
+
+=head2 Advance warning (delta)
+
+For some reminders, it is appropriate to receive advance warning of the event. For example, you may wish to be reminded of someone's birthday
+several days in advance. The delta portion of the REM command achieves this. It is specified as one or two "+" signs followed by a number n.
+Again, the difference between the "+" and "++" forms will be explained under the OMIT keyword. Remind will trigger the reminder on computed
+trigger date, as well as on each of the n days before the event. Here are some examples:
+
+C<REM 6 Jan +5 MSG Remind me of birthday 5 days in advance.>
+
+The above example would be triggered every 6th of January, as well as the 1st through 5th of January.
+
+=head2 Recurring events (repeat)
+
+However, events that do not repeat daily, weekly, monthly or yearly require another approach. The repeat component of the REM command fills this
+need. To use it, you must completely specify a date (year, month and day, and optionally weekday.) The repeat component is an asterisk
+followed by a number specifying the repetition period in days.
+
+For example, suppose you get paid every second Wednesday, and your last payday was Wednesday, 28 October, 1992. You can use:
+
+C<REM 28 Oct 1992 *14 MSG Payday>
+
+This issues the reminder every 14 days, starting from the calculated trigger date. You can use delta and back with repeat. Note, however, that the
+back is used only to compute the initial trigger date; thereafter, the reminder repeats with the specified period. Similarly, if you specify
+a weekday, it is used only to calculate the initial date, and does not affect the repetition period.
+
+=head1 REQUIRED TASKWARRIOR FORMAT
+
+The hook is only triggered when a new task is added with a "dft" tag and is "scheduled".
+
+=head2 Example using tdelta and trepeat (a remind command with AT/timed element)
+
+The syntax for tdelta and trepeat must be included in the task description. It matches the equivalent remind syntax (+10 and *1).
+These are removed from the description before saving and are used in the C<AT> clause in remind.
+
+=over
+
+=item
+
+C<task add Meaningless meeting +10 *1 +dft scheduled:2021-10-09T10:00Z>
+
+=back
+
+The C<Z> is optional, but the time specified is in Zulu time, so take that into account. When in BST it will take an hour off.
+
+Although this is a meaningless meeting, it is important enough to be reminded of it 10 minutes before 10am (C<+10>), with a repeat
+every minute (C<*1>) between the initial reminder and the time of the meeting itself.
+
+The additional C<tdelta> and C<trepeat> tags (+10 and *1) are removed from the task description before either getting to remind
+or to taskwarrior.
+
+=head2 Example using delta (a remind command with advanced warning in days)
+
+The only way that delta is different from tdelta inside the remind REM command is from it's placement: delta relates to the date aspect
+whereas tdelta relates to time in the C<AT> clause. We wish to retain the use of "+" but we must distinguise it inside the task description
+from tdelta so for delta we prefix with C<D>: e.g. C<D+10> which says that this must give us advance warning of 10 days. At this point, we
+are only using one C<+>, not two because use of the C<OMIT> keyword is not yet implemented.
+
+=over
+
+=item
+
+C<task add Meaningless meeting D+2 +dft scheduled:2021-10-09T10:00Z>
+
+=back
+
+This will pre-warn us 2 days in advance of the meaningless meeting scheduled to take place on 9 October 2021 at 11:00BST. The advance
+warning triggers will not trigger at the time of the meeting; instead the calendar will show that the meaningless meeting is happening in X days.
+
+=head2 Example using repeat (a remind command which creates a repeating event)
+
+This will set an event for the specified date/date and time and will repeat X days following.
+
+=over
+
+=item
+
+C<task add Meaningless meeting D*2 +dft scheduled:2021-10-09T10:00Z>
+=back
+All tokens: C<delta>, C<repeat>, C<tdelta> and C<trepeat> can be mixed and matched in the C<task> description.
+=cut
diff --git a/tw_hooks/simple_test.t b/tw_hooks/simple_test.t
index 5306a9b..b58f996 100644
--- a/tw_hooks/simple_test.t
+++ b/tw_hooks/simple_test.t
@@ -4,4 +4,4 @@ use warnings;
use Test::Simple tests => 2;
ok(2+2==4, "Addition of two numbers");
-ok(6-4==3, "Subtraction of two numbers");
+ok(6-4==2, "Subtraction of two numbers");