ledger_tiller_export 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +91 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +162 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/ledger_tiller_export.gemspec +32 -0
  14. data/lib/ledger_tiller_export.rb +180 -0
  15. data/lib/ledger_tiller_export/version.rb +4 -0
  16. data/sorbet/config +2 -0
  17. data/sorbet/rbi/gems/addressable.rbi +199 -0
  18. data/sorbet/rbi/gems/declarative-option.rbi +24 -0
  19. data/sorbet/rbi/gems/declarative.rbi +75 -0
  20. data/sorbet/rbi/gems/faraday.rbi +316 -0
  21. data/sorbet/rbi/gems/google-api-client.rbi +4822 -0
  22. data/sorbet/rbi/gems/google_drive.rbi +360 -0
  23. data/sorbet/rbi/gems/googleauth.rbi +172 -0
  24. data/sorbet/rbi/gems/httpclient.rbi +810 -0
  25. data/sorbet/rbi/gems/jwt.rbi +68 -0
  26. data/sorbet/rbi/gems/ledger_gen.rbi +39 -0
  27. data/sorbet/rbi/gems/memoist.rbi +42 -0
  28. data/sorbet/rbi/gems/mini_mime.rbi +52 -0
  29. data/sorbet/rbi/gems/multi_json.rbi +62 -0
  30. data/sorbet/rbi/gems/multipart-post.rbi +53 -0
  31. data/sorbet/rbi/gems/nokogiri.rbi +1007 -0
  32. data/sorbet/rbi/gems/os.rbi +46 -0
  33. data/sorbet/rbi/gems/public_suffix.rbi +18 -0
  34. data/sorbet/rbi/gems/rake.rbi +636 -0
  35. data/sorbet/rbi/gems/representable.rbi +276 -0
  36. data/sorbet/rbi/gems/retriable.rbi +21 -0
  37. data/sorbet/rbi/gems/rspec-core.rbi +194 -0
  38. data/sorbet/rbi/gems/rspec-expectations.rbi +859 -0
  39. data/sorbet/rbi/gems/rspec-mocks.rbi +192 -0
  40. data/sorbet/rbi/gems/rspec-support.rbi +38 -0
  41. data/sorbet/rbi/gems/rspec.rbi +15 -0
  42. data/sorbet/rbi/gems/signet.rbi +121 -0
  43. data/sorbet/rbi/gems/uber.rbi +26 -0
  44. data/sorbet/rbi/hidden-definitions/errors.txt +6574 -0
  45. data/sorbet/rbi/hidden-definitions/hidden.rbi +11607 -0
  46. data/sorbet/rbi/sorbet-typed/lib/bundler/all/bundler.rbi +8684 -0
  47. data/sorbet/rbi/sorbet-typed/lib/ruby/all/gem.rbi +4222 -0
  48. data/sorbet/rbi/sorbet-typed/lib/ruby/all/open3.rbi +111 -0
  49. data/sorbet/rbi/sorbet-typed/lib/ruby/all/resolv.rbi +543 -0
  50. metadata +190 -0
@@ -0,0 +1,180 @@
1
+ # coding: utf-8
2
+ # typed: strict
3
+
4
+ require 'ledger_tiller_export/version'
5
+ require 'ledger_gen'
6
+ require 'set'
7
+ require 'google_drive'
8
+ require 'csv'
9
+ require 'open3'
10
+ require 'sorbet-runtime'
11
+
12
+ module LedgerTillerExport
13
+ class Row < T::Struct
14
+ extend T::Sig
15
+
16
+ prop :txn_date, Date
17
+ prop :txn_id, String
18
+ prop :account, String
19
+ prop :amount, Float
20
+ prop :description, String
21
+
22
+ sig {params(row: T::Hash[String, T.nilable(String)]).returns(Row)}
23
+ def self.from_csv_row(row)
24
+ new(
25
+ txn_date: Date.strptime(T.must(row["Date"]), "%m/%d/%Y"),
26
+ txn_id: T.must(row['Transaction ID']),
27
+ account: T.must(row['Account']),
28
+ amount: T.must(row["Amount"]).gsub('$', '').gsub(',', '').to_f,
29
+ description: T.must(row['Description']).gsub(/\+? /, '').capitalize,
30
+ )
31
+ end
32
+ end
33
+
34
+ module RuleInterface
35
+ extend T::Sig
36
+ extend T::Helpers
37
+
38
+ interface!
39
+
40
+ sig {abstract.params(row: Row).returns(T.nilable(String))}
41
+ def account_for_row(row); end
42
+ end
43
+
44
+ class RegexpRule < T::Struct
45
+ extend T::Sig
46
+ include RuleInterface
47
+
48
+ const :match, Regexp
49
+ const :account, String
50
+
51
+ sig {override.params(row: Row).returns(T.nilable(String))}
52
+ def account_for_row(row)
53
+ if match.match(row.description)
54
+ return account
55
+ end
56
+
57
+ nil
58
+ end
59
+ end
60
+
61
+ class Exporter
62
+ extend T::Sig
63
+
64
+ sig {returns(T::Array[RuleInterface])}
65
+ attr_reader :rules
66
+
67
+ sig {returns(String)}
68
+ attr_reader :spreadsheet
69
+
70
+ sig {returns(String)}
71
+ attr_reader :worksheet
72
+
73
+ sig {returns(String)}
74
+ attr_reader :default_account
75
+
76
+ sig {returns(String)}
77
+ attr_reader :ledger_pretty_print_options
78
+
79
+ sig {returns(GoogleDrive::Session)}
80
+ attr_reader :session
81
+
82
+ sig {returns(LedgerGen::Journal)}
83
+ attr_reader :journal
84
+
85
+ sig do
86
+ params(
87
+ rules: T::Array[RuleInterface],
88
+ spreadsheet: String,
89
+ worksheet: String,
90
+ default_account: String,
91
+ ledger_pretty_print_options: String,
92
+ ledger_date_format: String,
93
+ ).void
94
+ end
95
+ def initialize(rules:, spreadsheet:, worksheet: 'Transactions', default_account: 'Expenses:Misc', ledger_pretty_print_options: '--sort=date', ledger_date_format: '%m/%d')
96
+ @rules = T.let(rules, T::Array[RuleInterface])
97
+ @spreadsheet = T.let(spreadsheet, String)
98
+ @worksheet = T.let(worksheet, String)
99
+ @default_account = T.let(default_account, String)
100
+ @ledger_pretty_print_options = T.let(ledger_pretty_print_options, String)
101
+
102
+ @session = T.let(GoogleDrive::Session.from_config('.config.json'), GoogleDrive::Session)
103
+ @journal = T.let(LedgerGen::Journal.new, LedgerGen::Journal)
104
+
105
+ @journal.date_format = ledger_date_format
106
+ end
107
+
108
+ sig {void}
109
+ def run
110
+ @known_transactions = T.let(fetch_known_transactions, T.nilable(T::Set[String]))
111
+ @known_transactions = T.must(@known_transactions)
112
+
113
+ worksheets = session.spreadsheet_by_key(spreadsheet).worksheets
114
+
115
+ ws = worksheets.detect { |w| w.title == worksheet }
116
+ raw_csv = ws.export_as_string.force_encoding('utf-8')
117
+ csv = CSV.parse(raw_csv, headers: true)
118
+
119
+ T.must(csv).each do |raw_row|
120
+ row = Row.from_csv_row(raw_row.to_h)
121
+
122
+ next if @known_transactions.include?(row.txn_id)
123
+ next if skip_row?(row)
124
+
125
+ journal_transaction(row)
126
+ end
127
+
128
+ puts @journal.pretty_print(@ledger_pretty_print_options)
129
+ end
130
+
131
+ sig {params(row: Row).returns(T::Boolean)}
132
+ def skip_row?(row)
133
+ false
134
+ end
135
+
136
+ sig {returns(T::Set[String])}
137
+ def fetch_known_transactions
138
+ cmd = [
139
+ 'ledger',
140
+ %q{--register-format=%(tag("tiller_id"))\n},
141
+ 'reg',
142
+ 'expr',
143
+ 'has_tag(/tiller_id/)',
144
+ ]
145
+
146
+ raw_tags, err, status = Open3.capture3(*cmd)
147
+
148
+ if status.exitstatus && status.exitstatus != 0
149
+ STDERR.puts(err)
150
+ exit T.must(status.exitstatus)
151
+ end
152
+
153
+ Set.new(raw_tags.strip.split(/(\n|,)/).map(&:strip))
154
+ end
155
+
156
+ sig {params(row: Row).returns(String)}
157
+ def account_for_row(row)
158
+ rules.each do |rule|
159
+ account = rule.account_for_row(row)
160
+ return account if account
161
+ end
162
+
163
+ default_account
164
+ end
165
+
166
+ sig {params(row: Row).void}
167
+ def journal_transaction(row)
168
+ journal.transaction do |txn|
169
+ txn.cleared!
170
+ txn.date row.txn_date
171
+ txn.payee row.description
172
+ txn.comment "tiller_id: #{row.txn_id}"
173
+
174
+ txn.posting account_for_row(row), row.amount * -1
175
+ txn.posting row.account
176
+ end
177
+ end
178
+
179
+ end
180
+ end
@@ -0,0 +1,4 @@
1
+ # typed: strict
2
+ module LedgerTillerExport
3
+ VERSION = "0.1.0"
4
+ end
@@ -0,0 +1,2 @@
1
+ --dir
2
+ .
@@ -0,0 +1,199 @@
1
+ # This file is autogenerated. Do not edit it by hand. Regenerate it with:
2
+ # srb rbi gems
3
+
4
+ # typed: strict
5
+ #
6
+ # If you would like to make changes to this file, great! Please create the gem's shim here:
7
+ #
8
+ # https://github.com/sorbet/sorbet-typed/new/master?filename=lib/addressable/all/addressable.rbi
9
+ #
10
+ # addressable-2.7.0
11
+
12
+ module Addressable
13
+ end
14
+ module Addressable::VERSION
15
+ end
16
+ module Addressable::IDNA
17
+ def self.lookup_unicode_combining_class(codepoint); end
18
+ def self.lookup_unicode_compatibility(codepoint); end
19
+ def self.lookup_unicode_composition(unpacked); end
20
+ def self.lookup_unicode_lowercase(codepoint); end
21
+ def self.punycode_adapt(delta, numpoints, firsttime); end
22
+ def self.punycode_basic?(codepoint); end
23
+ def self.punycode_decode(punycode); end
24
+ def self.punycode_decode_digit(codepoint); end
25
+ def self.punycode_delimiter?(codepoint); end
26
+ def self.punycode_encode(unicode); end
27
+ def self.punycode_encode_digit(d); end
28
+ def self.to_ascii(input); end
29
+ def self.to_unicode(input); end
30
+ def self.unicode_compose(unpacked); end
31
+ def self.unicode_compose_pair(ch_one, ch_two); end
32
+ def self.unicode_decompose(unpacked); end
33
+ def self.unicode_decompose_hangul(codepoint); end
34
+ def self.unicode_downcase(input); end
35
+ def self.unicode_normalize_kc(input); end
36
+ def self.unicode_sort_canonical(unpacked); end
37
+ end
38
+ class Addressable::IDNA::PunycodeBadInput < StandardError
39
+ end
40
+ class Addressable::IDNA::PunycodeBigOutput < StandardError
41
+ end
42
+ class Addressable::IDNA::PunycodeOverflow < StandardError
43
+ end
44
+ class Addressable::URI
45
+ def +(uri); end
46
+ def ==(uri); end
47
+ def ===(uri); end
48
+ def absolute?; end
49
+ def authority; end
50
+ def authority=(new_authority); end
51
+ def basename; end
52
+ def default_port; end
53
+ def defer_validation; end
54
+ def display_uri; end
55
+ def domain; end
56
+ def dup; end
57
+ def empty?; end
58
+ def eql?(uri); end
59
+ def extname; end
60
+ def fragment; end
61
+ def fragment=(new_fragment); end
62
+ def freeze; end
63
+ def hash; end
64
+ def host; end
65
+ def host=(new_host); end
66
+ def hostname; end
67
+ def hostname=(new_hostname); end
68
+ def inferred_port; end
69
+ def initialize(options = nil); end
70
+ def inspect; end
71
+ def ip_based?; end
72
+ def join!(uri); end
73
+ def join(uri); end
74
+ def merge!(uri); end
75
+ def merge(hash); end
76
+ def normalize!; end
77
+ def normalize; end
78
+ def normalized_authority; end
79
+ def normalized_fragment; end
80
+ def normalized_host; end
81
+ def normalized_password; end
82
+ def normalized_path; end
83
+ def normalized_port; end
84
+ def normalized_query(*flags); end
85
+ def normalized_scheme; end
86
+ def normalized_site; end
87
+ def normalized_user; end
88
+ def normalized_userinfo; end
89
+ def omit!(*components); end
90
+ def omit(*components); end
91
+ def origin; end
92
+ def origin=(new_origin); end
93
+ def password; end
94
+ def password=(new_password); end
95
+ def path; end
96
+ def path=(new_path); end
97
+ def port; end
98
+ def port=(new_port); end
99
+ def query; end
100
+ def query=(new_query); end
101
+ def query_values(return_type = nil); end
102
+ def query_values=(new_query_values); end
103
+ def relative?; end
104
+ def remove_composite_values; end
105
+ def replace_self(uri); end
106
+ def request_uri; end
107
+ def request_uri=(new_request_uri); end
108
+ def route_from(uri); end
109
+ def route_to(uri); end
110
+ def scheme; end
111
+ def scheme=(new_scheme); end
112
+ def self.convert_path(path); end
113
+ def self.encode(uri, return_type = nil); end
114
+ def self.encode_component(component, character_class = nil, upcase_encoded = nil); end
115
+ def self.escape(uri, return_type = nil); end
116
+ def self.form_encode(form_values, sort = nil); end
117
+ def self.form_unencode(encoded_value); end
118
+ def self.heuristic_parse(uri, hints = nil); end
119
+ def self.ip_based_schemes; end
120
+ def self.join(*uris); end
121
+ def self.normalize_component(component, character_class = nil, leave_encoded = nil); end
122
+ def self.normalize_path(path); end
123
+ def self.normalized_encode(uri, return_type = nil); end
124
+ def self.parse(uri); end
125
+ def self.port_mapping; end
126
+ def self.unencode(uri, return_type = nil, leave_encoded = nil); end
127
+ def self.unencode_component(uri, return_type = nil, leave_encoded = nil); end
128
+ def self.unescape(uri, return_type = nil, leave_encoded = nil); end
129
+ def self.unescape_component(uri, return_type = nil, leave_encoded = nil); end
130
+ def site; end
131
+ def site=(new_site); end
132
+ def split_path(path); end
133
+ def tld; end
134
+ def tld=(new_tld); end
135
+ def to_hash; end
136
+ def to_s; end
137
+ def to_str; end
138
+ def user; end
139
+ def user=(new_user); end
140
+ def userinfo; end
141
+ def userinfo=(new_userinfo); end
142
+ def validate; end
143
+ end
144
+ class Addressable::URI::InvalidURIError < StandardError
145
+ end
146
+ module Addressable::URI::CharacterClasses
147
+ end
148
+ class Addressable::Template
149
+ def ==(template); end
150
+ def eql?(template); end
151
+ def expand(mapping, processor = nil, normalize_values = nil); end
152
+ def extract(uri, processor = nil); end
153
+ def freeze; end
154
+ def generate(params = nil, recall = nil, options = nil); end
155
+ def initialize(pattern); end
156
+ def inspect; end
157
+ def join_values(operator, return_value); end
158
+ def keys; end
159
+ def match(uri, processor = nil); end
160
+ def named_captures; end
161
+ def names; end
162
+ def normalize_keys(mapping); end
163
+ def normalize_value(value); end
164
+ def ordered_variable_defaults; end
165
+ def parse_template_pattern(pattern, processor = nil); end
166
+ def partial_expand(mapping, processor = nil, normalize_values = nil); end
167
+ def pattern; end
168
+ def source; end
169
+ def to_regexp; end
170
+ def transform_capture(mapping, capture, processor = nil, normalize_values = nil); end
171
+ def transform_partial_capture(mapping, capture, processor = nil, normalize_values = nil); end
172
+ def variable_defaults; end
173
+ def variables; end
174
+ end
175
+ class Addressable::Template::InvalidTemplateValueError < StandardError
176
+ end
177
+ class Addressable::Template::InvalidTemplateOperatorError < StandardError
178
+ end
179
+ class Addressable::Template::TemplateOperatorAbortedError < StandardError
180
+ end
181
+ class Addressable::Template::MatchData
182
+ def [](key, len = nil); end
183
+ def captures; end
184
+ def initialize(uri, template, mapping); end
185
+ def inspect; end
186
+ def keys; end
187
+ def mapping; end
188
+ def names; end
189
+ def post_match; end
190
+ def pre_match; end
191
+ def string; end
192
+ def template; end
193
+ def to_a; end
194
+ def to_s; end
195
+ def uri; end
196
+ def values; end
197
+ def values_at(*indexes); end
198
+ def variables; end
199
+ end
@@ -0,0 +1,24 @@
1
+ # This file is autogenerated. Do not edit it by hand. Regenerate it with:
2
+ # srb rbi gems
3
+
4
+ # typed: strict
5
+ #
6
+ # If you would like to make changes to this file, great! Please create the gem's shim here:
7
+ #
8
+ # https://github.com/sorbet/sorbet-typed/new/master?filename=lib/declarative-option/all/declarative-option.rbi
9
+ #
10
+ # declarative-option-0.1.0
11
+
12
+ module Declarative
13
+ def self.Option(value, options = nil); end
14
+ end
15
+ module Declarative::Callable
16
+ end
17
+ class Declarative::Option
18
+ def call(value, options = nil); end
19
+ def callable?(value, options); end
20
+ def lambda_for_callable(value, options); end
21
+ def lambda_for_proc(value, options); end
22
+ def lambda_for_static(value, options); end
23
+ def lambda_for_symbol(value, options); end
24
+ end
@@ -0,0 +1,75 @@
1
+ # This file is autogenerated. Do not edit it by hand. Regenerate it with:
2
+ # srb rbi gems
3
+
4
+ # typed: true
5
+ #
6
+ # If you would like to make changes to this file, great! Please create the gem's shim here:
7
+ #
8
+ # https://github.com/sorbet/sorbet-typed/new/master?filename=lib/declarative/all/declarative.rbi
9
+ #
10
+ # declarative-0.0.10
11
+
12
+ module Declarative
13
+ end
14
+ class Declarative::Definitions < Hash
15
+ def add(name, options = nil, &block); end
16
+ def build_nested(options); end
17
+ def each(&block); end
18
+ def get(name); end
19
+ def initialize(definition_class); end
20
+ end
21
+ class Declarative::Definitions::Definition
22
+ def [](name); end
23
+ def initialize(name, options = nil, &block); end
24
+ def merge!(hash); end
25
+ def merge(hash); end
26
+ end
27
+ class Declarative::Defaults
28
+ def call(name, given_options); end
29
+ def handle_array_and_deprecate(variables); end
30
+ def initialize; end
31
+ def merge!(hash = nil, &block); end
32
+ def self.wrap_arrays(variables); end
33
+ end
34
+ class Declarative::Variables
35
+ def self.Append(appended_array); end
36
+ def self.Merge(merged_hash); end
37
+ def self.merge(defaults, overrides); end
38
+ end
39
+ class Declarative::Variables::Proc < Proc
40
+ end
41
+ class Declarative::DeepDup
42
+ def self.call(args); end
43
+ def self.dup_items(arr); end
44
+ end
45
+ class Declarative::Heritage < Array
46
+ def call!(inheritor, cfg); end
47
+ def call(inheritor, &block); end
48
+ def record(method, *args, &block); end
49
+ end
50
+ module Declarative::Heritage::DSL
51
+ def heritage; end
52
+ end
53
+ module Declarative::Heritage::Inherited
54
+ def inherited(subclass); end
55
+ end
56
+ module Declarative::Heritage::Included
57
+ def included(mod); end
58
+ end
59
+ module Declarative::Schema
60
+ def self.extended(extender); end
61
+ end
62
+ module Declarative::Schema::DSL
63
+ def _defaults; end
64
+ def build_definition(name, options = nil, &block); end
65
+ def defaults(options = nil, &block); end
66
+ def definition_class; end
67
+ def definitions; end
68
+ def nested_builder; end
69
+ def property(name, options = nil, &block); end
70
+ def wrap_arrays_from_block(block); end
71
+ end
72
+ module Declarative::Schema::Feature
73
+ def feature(*mods); end
74
+ def register_feature(mod); end
75
+ end