git ssb

0+

cel / ledger-scripts



Commit 1d0bfddc48640e483c022b27568c41a6015e9321

Add ledger-combine script

Charles Lehner committed on 11/1/2015, 1:59:21 AM
Parent: f357ae91ba3f3d3aa2221475e5eb22e74fbcfb55

Files changed

README.mdchanged
ledger-combineadded
README.mdView
@@ -4,9 +4,9 @@
44 A collection of scripts for helping to organize one's finances using
55 [ledger](ledger-cli.org),
66 with a focus on crypto-currencies.
77
8-© 2013 Charles Lehner, [MIT License](http://cel.mit-license.org/)
8+© 2013-2015 Charles Lehner, [MIT License](http://cel.mit-license.org/)
99
1010 Data import scripts
1111 -------------------
1212
@@ -124,8 +124,13 @@
124124 merge transactions between journals that might be the same.
125125
126126 *Not completely working*
127127
128+#### `ledger-combine`
129+
130+Merge multiple ledger journals into one, in time order, without detecting
131+duplicates.
132+
128133 Similar projects
129134 ----------------
130135
131136 * [ledger-bitcoin](https://github.com/profmaad/ledger-bitcoin) by
ledger-combineView
@@ -1,0 +1,123 @@
1+#!/usr/bin/env perl
2+
3+use strict;
4+use warnings;
5+use Time::Piece;
6+use Time::Seconds 'ONE_DAY';
7+
8+$| = 1;
9+
10+my @files;
11+my @buffer;
12+
13+# acceptable time difference to consider transactions matching
14+my $time_difference = 7 * ONE_DAY;
15+
16+my $dateformat = "%Y/%m/%d";
17+
18+if ($#ARGV == -1) {
19+ print STDERR "Usage: $0 [journal.ledger]...\n";
20+ exit 1;
21+}
22+
23+my @in_filenames = @ARGV;
24+my $out_filename;
25+
26+if ($out_filename) {
27+ open OUTFILE, '>', $out_filename;
28+ OUTFILE->autoflush(1);
29+} else {
30+ open OUTFILE, '>&STDOUT';
31+}
32+
33+sub read_entry {
34+ my $fh = shift;
35+ return if !defined $fh or eof $fh;
36+
37+ my $amounts = {};
38+ my $lines = [];
39+ my $entry = {
40+ lines => $lines,
41+ amounts => $amounts,
42+ fh => $fh,
43+ };
44+
45+ # Read lines until found entry date
46+ while(1) {
47+ my $line = <$fh>;
48+ unless (defined $line) {
49+ # fh is closed
50+ return;
51+ }
52+ push @$lines, $line;
53+ if ($line =~ /^([0-9]{4}[\/-][0-9]{1,2}[\/-][0-9]{1,2})/) {
54+ # Make the date string lexically comparable
55+ $entry->{date} = Time::Piece->strptime($1, $dateformat) or die("$!");
56+ last;
57+ }
58+ }
59+
60+ # Read until reaching a blank line
61+ while(1) {
62+ my $line = <$fh>;
63+ unless (defined $line) {
64+ # fh is closed
65+ push @$lines, "\n";
66+ return $entry;
67+ }
68+ push @$lines, $line;
69+ return $entry if $line =~ /^$/;
70+ # Record item amounts
71+ if ($line =~ /^\s*[^;].*?(?:\s\s|\t)\s*[^@ ]*? ?[^ 0-9]?-?([0-9.,]+)0*[^@]*?\s+(?:;.*|@.*)?$/) {
72+ # Account 35.0 BTC @ $15.19
73+ $amounts->{+$1} = 1;
74+ }
75+ }
76+
77+ return $entry;
78+}
79+
80+sub print_entry {
81+ my ($stream, $entry) = @_;
82+ my $date = $entry->{date};
83+ print $stream $_ for @{ $entry->{lines} };
84+}
85+
86+sub pop_oldest_entry {
87+ return unless scalar @buffer;
88+
89+ # pick the oldest entry
90+ my $oldest = $buffer[0];
91+ for my $entry (@buffer[1..$#buffer]) {
92+ if ($entry->{date} < $oldest->{date}) {
93+ $oldest = $entry;
94+ }
95+ }
96+
97+ if ($oldest) {
98+ # remove the entry from the buffer
99+ @buffer = grep { $_ != $oldest } @buffer;
100+ # read a new entry from the same stream and add it to the buffer
101+ my $fh = $oldest->{fh};
102+ if (my $new_entry = read_entry $fh) {
103+ push @buffer, $new_entry;
104+ }
105+ }
106+
107+ return $oldest;
108+}
109+
110+# Open the ledger journals
111+for my $filename (@in_filenames) {
112+ open my $fh, '<', $filename or die "Unable to open file $filename: $!";
113+ push @files, $fh;
114+}
115+
116+# Read first from each journal
117+for my $fh (@files) {
118+ push @buffer, read_entry($fh);
119+}
120+
121+while (my $entry = pop_oldest_entry) {
122+ print_entry \*OUTFILE, $entry;
123+}

Built with git-ssb-web