ledger_tiller_export 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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