rock_books 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +200 -0
- data/RELEASE_NOTES.md +4 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/rock_books +5 -0
- data/lib/rock_books/cmd_line/command_line_interface.rb +391 -0
- data/lib/rock_books/cmd_line/main.rb +108 -0
- data/lib/rock_books/documents/book_set.rb +113 -0
- data/lib/rock_books/documents/chart_of_accounts.rb +113 -0
- data/lib/rock_books/documents/journal.rb +161 -0
- data/lib/rock_books/documents/journal_entry.rb +73 -0
- data/lib/rock_books/documents/journal_entry_builder.rb +148 -0
- data/lib/rock_books/errors/account_not_found_error.rb +20 -0
- data/lib/rock_books/errors/error.rb +10 -0
- data/lib/rock_books/filters/acct_amount_filters.rb +12 -0
- data/lib/rock_books/filters/journal_entry_filters.rb +84 -0
- data/lib/rock_books/helpers/book_set_loader.rb +62 -0
- data/lib/rock_books/helpers/parse_helper.rb +22 -0
- data/lib/rock_books/reports/balance_sheet.rb +60 -0
- data/lib/rock_books/reports/income_statement.rb +63 -0
- data/lib/rock_books/reports/multidoc_transaction_report.rb +66 -0
- data/lib/rock_books/reports/receipts_report.rb +57 -0
- data/lib/rock_books/reports/report_context.rb +15 -0
- data/lib/rock_books/reports/reporter.rb +118 -0
- data/lib/rock_books/reports/transaction_report.rb +103 -0
- data/lib/rock_books/reports/tx_by_account.rb +82 -0
- data/lib/rock_books/reports/tx_one_account.rb +63 -0
- data/lib/rock_books/types/account.rb +7 -0
- data/lib/rock_books/types/account_type.rb +33 -0
- data/lib/rock_books/types/acct_amount.rb +52 -0
- data/lib/rock_books/version.rb +3 -0
- data/lib/rock_books.rb +7 -0
- data/rock_books.gemspec +39 -0
- data/sample_data/minimal/rockbooks-inputs/2017-xyz-chart-of-accounts.rbt +62 -0
- data/sample_data/minimal/rockbooks-inputs/2017-xyz-checking-journal.rbt +17 -0
- data/sample_data/minimal/rockbooks-inputs/2017-xyz-general-journal.rbt +14 -0
- data/sample_data/minimal/rockbooks-inputs/2017-xyz-visa-journal.rbt +23 -0
- metadata +158 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 42c5b49d705d543e5fbf5cf1f311e7b809a7511b898c83b372ee00b0922a8233
|
4
|
+
data.tar.gz: '0090982673c619217d19858dcad25bf05971e6c44de006fc778ed544a74b46e5'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 60037d15787e0b3559e38050a4e26538ef111de98dbd24ea04e59869d33468869c279e924e91347f8214c168d3a758a64823b2d58f1bc3af18ac60b2115dfa5b
|
7
|
+
data.tar.gz: e96e3afde04b586a3692c4bed2e6cb854aadea35fcbe1a6a5a09b7b80585c85cdc22fb70dc76aa8ca5c316f640e602426a74d3be50eb6dd16b9f3b73654f3706
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Keith Bennett
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
# RockBooks
|
2
|
+
|
3
|
+
A super primitive bookkeeping system using text files as input documents and console output
|
4
|
+
for reporting.
|
5
|
+
|
6
|
+
A supreme goal of this project is to give _you_ control over your data.
|
7
|
+
Want to serialize it to YAML, JSON, CSV, or manipulate it in your custom code?
|
8
|
+
No problem!
|
9
|
+
|
10
|
+
It assumes the traditional double entry bookkeeping system, with debits and credits.
|
11
|
+
In general, assets and expenses are debit balance accounts, and income, liabilities and equity
|
12
|
+
are credit balance accounts.
|
13
|
+
|
14
|
+
So, to really have this software make sense to you, you should probably understand
|
15
|
+
the double entry bookkeeping paradigm pretty well.
|
16
|
+
|
17
|
+
# Terminology Usage
|
18
|
+
|
19
|
+
* document - a RockBooks logical document such as a chart of accounts, a journal, etc.,
|
20
|
+
usually containing information parsed from a data file
|
21
|
+
|
22
|
+
* data file - a RockBooks data file, which is a text file, which
|
23
|
+
by convention has the extension `.rbt`
|
24
|
+
|
25
|
+
|
26
|
+
## Data File Format
|
27
|
+
|
28
|
+
Lines beginning with `#` will be ignored.
|
29
|
+
|
30
|
+
Data lines that contain the value of document properties,
|
31
|
+
as opposed to transactions, etc., will be expressed as lines beginning with `@`:
|
32
|
+
|
33
|
+
```
|
34
|
+
@doc_type: journal
|
35
|
+
@title: "ABC Bank Checking Account Disbursements Journal"
|
36
|
+
@account: ck_abc
|
37
|
+
```
|
38
|
+
|
39
|
+
Repeating data types such as entries in journals, and accounts in the chart of accounts,
|
40
|
+
should in general be input after the properties.
|
41
|
+
|
42
|
+
Data lines will contain fields that an be separated with an arbitrary number of spaces, e.g.:
|
43
|
+
|
44
|
+
```
|
45
|
+
2018-05-18 123.45 703
|
46
|
+
```
|
47
|
+
|
48
|
+
In journals, all entries will begin with dates, and all dates begin with numerals, so the
|
49
|
+
presence of a numeral in the first column will be interpreted as the beginning of a new
|
50
|
+
transaction (entry). Any lines following it not beginning with a `#` or number will be
|
51
|
+
assumed to be the textual description of the transaction, and will be saved along with
|
52
|
+
its other data.
|
53
|
+
|
54
|
+
In order to make the entry of dates more convenient, many documents will support
|
55
|
+
a `@date_prefix` property that will be prepended to dates. For example, if this prefix
|
56
|
+
contains `2018-`, then subsequent dates must exclude that prefix since it will be
|
57
|
+
automatically prepended. So, for example, a journal might contain the following lines:
|
58
|
+
|
59
|
+
```
|
60
|
+
@date_prefix: 2018-
|
61
|
+
# ...more lines...
|
62
|
+
05-29 37.50 ofc.spls
|
63
|
+
05-30 22.20 tr.taxi
|
64
|
+
```
|
65
|
+
|
66
|
+
All date strings must use the format `YYYY-MM-DD`, because that's what will be expected
|
67
|
+
by the application when it converts the date strings into numeric dates.
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
### Chart of Accounts
|
72
|
+
|
73
|
+
Pretty much everything in this application assumes the presence of a chart of accounts
|
74
|
+
listing the accounts, including their codes, types, and names.
|
75
|
+
|
76
|
+
You'll need to provide a chart of accounts file that includes the following line in the header:
|
77
|
+
|
78
|
+
`@document_type: chart_of_accounts`
|
79
|
+
|
80
|
+
This file should contain the accounts
|
81
|
+
that will be used. Each account should contain the following fields:
|
82
|
+
|
83
|
+
| Property Name | Description |
|
84
|
+
| ------------- | ------------- |
|
85
|
+
| code | a short string with which to identify an account, e.g. `ret.earn` for retained earnings
|
86
|
+
| type | 'A' for asset, 'L' for liability, 'O' for (owners) equity, 'I' for income, and 'E' for expenses.
|
87
|
+
| name | a longer more descriptive name, used in reports, so no more than 30 or so characters long is recommended
|
88
|
+
|
89
|
+
|
90
|
+
So, the chart of accounts data might include something like this:
|
91
|
+
|
92
|
+
```
|
93
|
+
ck.xyz A XYZ Bank Checking Account
|
94
|
+
loan.owner L Loan Payable to Owner
|
95
|
+
o.equity O Owner's Equity
|
96
|
+
sls.cons I Consulting Sales
|
97
|
+
tr.airfare E Travel - Air Fare
|
98
|
+
```
|
99
|
+
|
100
|
+
Although hyphens and underscores are typically used to logically separate string fragments,
|
101
|
+
we recommend periods; they're much easier to type, and you'll be doing a lot of that.
|
102
|
+
|
103
|
+
There is no maximum length for account codes, and reports will automatically align based
|
104
|
+
on the longest account code. However, keep in mind that you will need to type these codes,
|
105
|
+
and they will consume space in reports.
|
106
|
+
|
107
|
+
### Journals
|
108
|
+
|
109
|
+
Journals (also referred to as _documents_ by this application)
|
110
|
+
are used to record transactions of, for example:
|
111
|
+
|
112
|
+
* cash disbursements (expenditures for a single checking account)
|
113
|
+
* cash receipts (funds coming into a single checking account)
|
114
|
+
* combined cash disbursements and receipts
|
115
|
+
* a credit card account
|
116
|
+
* a Paypal account
|
117
|
+
* sales
|
118
|
+
|
119
|
+
Each journal data file needs to contain:
|
120
|
+
|
121
|
+
`@doc_type: journal`
|
122
|
+
|
123
|
+
Also, it needs to identify the code of the account the journal is representing.
|
124
|
+
So for example, if it is a journal of a PayPal account, and the PayPal
|
125
|
+
account's code is `paypal`, then you'll need a line like this in your journal file:
|
126
|
+
|
127
|
+
`@account_code: paypal`
|
128
|
+
|
129
|
+
For your convenience, when entering transactions in a journal (but _not_ a _general_ journal),
|
130
|
+
you may enter all numbers going in the direction natural for that journal as positive numbers.
|
131
|
+
|
132
|
+
For example, a _Cash Disbursements Journal_ (something like a
|
133
|
+
check register) may contain a transaction like this:
|
134
|
+
|
135
|
+
```
|
136
|
+
05-29 37.50 ofc.spls
|
137
|
+
```
|
138
|
+
|
139
|
+
There may be many transactions in your journal, and it would be cumbersome to have to
|
140
|
+
type minus signs in front of all of them if they were credits.
|
141
|
+
|
142
|
+
Because of this, the program allows you to configure each journal as to the direction
|
143
|
+
(debit or credit) of the transaction. This is done with the `@debit_or_credit` property.
|
144
|
+
|
145
|
+
For an asset journal whose numbers will be crediting the main account
|
146
|
+
(e.g. a cash disbursements journal whose entries will primarily be crediting
|
147
|
+
the cash account), you would set the property to `debit`:
|
148
|
+
|
149
|
+
```
|
150
|
+
@debit_or_credit: debit
|
151
|
+
```
|
152
|
+
|
153
|
+
|
154
|
+
#### General Journal
|
155
|
+
|
156
|
+
The general journal is a special form of journal that does not have a primary account.
|
157
|
+
|
158
|
+
In this journal, debits and credits need to be specified literally as account code/amount
|
159
|
+
pairs, where positive numbers will result in debits, and negative numbers will result in credits, e.g.:
|
160
|
+
|
161
|
+
```
|
162
|
+
03-10 tr.perdiem.mi 495.00 loan.to.sh -495.00
|
163
|
+
Per Diem allowance for conference trip
|
164
|
+
```
|
165
|
+
|
166
|
+
|
167
|
+
|
168
|
+
## Installation
|
169
|
+
|
170
|
+
Add this line to your application's Gemfile:
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
gem 'rock_books'
|
174
|
+
```
|
175
|
+
|
176
|
+
And then execute:
|
177
|
+
|
178
|
+
$ bundle
|
179
|
+
|
180
|
+
Or install it yourself as:
|
181
|
+
|
182
|
+
$ gem install rock_books
|
183
|
+
|
184
|
+
## Usage
|
185
|
+
|
186
|
+
TODO: Write usage instructions here
|
187
|
+
|
188
|
+
## Development
|
189
|
+
|
190
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
191
|
+
|
192
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
193
|
+
|
194
|
+
## Contributing
|
195
|
+
|
196
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/rock_books.
|
197
|
+
|
198
|
+
## License
|
199
|
+
|
200
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/RELEASE_NOTES.md
ADDED
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "rock_books"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/rock_books
ADDED
@@ -0,0 +1,391 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
require_relative '../../rock_books'
|
6
|
+
require_relative '../version'
|
7
|
+
require_relative '../reports/reporter'
|
8
|
+
require_relative '../helpers/book_set_loader'
|
9
|
+
|
10
|
+
module RockBooks
|
11
|
+
|
12
|
+
class CommandLineInterface
|
13
|
+
|
14
|
+
# Enable users to type methods of this class more conveniently:
|
15
|
+
include JournalEntryFilters
|
16
|
+
extend Forwardable
|
17
|
+
|
18
|
+
attr_reader :book_set, :interactive_mode, :run_options, :verbose_mode
|
19
|
+
|
20
|
+
|
21
|
+
class Command < Struct.new(:min_string, :max_string, :action); end
|
22
|
+
|
23
|
+
|
24
|
+
class BadCommandError < RuntimeError; end
|
25
|
+
|
26
|
+
|
27
|
+
# Enable use of some BookSet methods in shell with long and short names aaa, ae:
|
28
|
+
|
29
|
+
def_delegator :book_set, :all_acct_amounts
|
30
|
+
def_delegator :book_set, :all_acct_amounts, :aaa
|
31
|
+
|
32
|
+
def_delegator :book_set, :all_entries
|
33
|
+
def_delegator :book_set, :all_entries, :ae
|
34
|
+
|
35
|
+
def_delegator :book_set, :chart_of_accounts
|
36
|
+
def_delegator :book_set, :chart_of_accounts, :chart
|
37
|
+
|
38
|
+
# For conveniently finding the project on Github from the shell
|
39
|
+
PROJECT_URL = 'https://github.com/keithrbennett/rock_books'
|
40
|
+
|
41
|
+
# Help text to be used when requested by 'h' command, in case of unrecognized or nonexistent command, etc.
|
42
|
+
HELP_TEXT = "
|
43
|
+
Command Line Switches: [rock-books version #{RockBooks::VERSION} at https://github.com/keithrbennett/rock_books]
|
44
|
+
|
45
|
+
-i input directory specification, default: '#{DEFAULT_INPUT_DIR}'
|
46
|
+
-o output (reports) directory specification, default: '#{DEFAULT_OUTPUT_DIR}'
|
47
|
+
-r receipts directory, default: '#{DEFAULT_RECEIPT_DIR}'
|
48
|
+
-s run in shell mode
|
49
|
+
|
50
|
+
Commands:
|
51
|
+
|
52
|
+
rec[eipts] - receipts: a/:a all, m/:m missing, e/:e existing
|
53
|
+
rep[orts] - return an OpenStruct containing all reports (interactive shell mode only)
|
54
|
+
d[isplay_reports] - display all reports on stdout
|
55
|
+
w[rite_reports] - write all reports to the output directory (see -o option)
|
56
|
+
c[hart_of_accounts] - chart of accounts
|
57
|
+
h[elp] - prints this help
|
58
|
+
jo[urnals] - list of the journals' short names
|
59
|
+
rel[oad_data] - reload data from input files
|
60
|
+
q[uit] - exits this program (interactive shell mode only) (see also 'x')
|
61
|
+
x[it] - exits this program (interactive shell mode only) (see also 'q')
|
62
|
+
|
63
|
+
When in interactive shell mode:
|
64
|
+
* use quotes for string parameters such as method names.
|
65
|
+
* for pry commands, use prefix `%`.
|
66
|
+
* you can use the global variable $filter to filter reports
|
67
|
+
|
68
|
+
"
|
69
|
+
|
70
|
+
def initialize(run_options)
|
71
|
+
@run_options = run_options
|
72
|
+
@interactive_mode = !!(run_options.interactive_mode)
|
73
|
+
@verbose_mode = run_options.verbose
|
74
|
+
|
75
|
+
validate_run_options(run_options)
|
76
|
+
# book_set is set with a lazy initializer
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def validate_run_options(options)
|
81
|
+
|
82
|
+
validate_input_dir = -> do
|
83
|
+
File.directory?(options.input_dir) ? nil : "Input directory '#{options.input_dir}' does not exist. "
|
84
|
+
end
|
85
|
+
|
86
|
+
validate_output_dir = -> do
|
87
|
+
dir = options.output_dir
|
88
|
+
subdir = File.join(dir, SINGLE_ACCT_SUBDIR)
|
89
|
+
|
90
|
+
# We need to create the reports directory and its single-account subdirectory.
|
91
|
+
# We can accomplish both by creating just the subdirectory.
|
92
|
+
FileUtils.mkdir_p(subdir) ? nil : \
|
93
|
+
"Output directory '#{dir}' and/or #{subdir} does not exist and could not be created. "
|
94
|
+
end
|
95
|
+
|
96
|
+
validate_receipts_dir = -> do
|
97
|
+
File.directory?(options.receipt_dir) ? nil : \
|
98
|
+
"Receipts directory '#{options.receipt_dir}' does not exist. " +
|
99
|
+
"If you do not want receipt handling, use the --no-receipts command line option."
|
100
|
+
end
|
101
|
+
|
102
|
+
output = []
|
103
|
+
output << validate_input_dir.()
|
104
|
+
output << validate_output_dir.()
|
105
|
+
if run_options.do_receipts
|
106
|
+
output << validate_receipts_dir.()
|
107
|
+
end
|
108
|
+
|
109
|
+
unless output.empty?
|
110
|
+
message = output.compact.join("\n") << "\n"
|
111
|
+
raise Error.new(message)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def print_help
|
117
|
+
puts HELP_TEXT
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
def enclose_in_hyphen_lines(string)
|
122
|
+
hyphen_line = "#{'-' * 80}\n"
|
123
|
+
hyphen_line + string + "\n" + hyphen_line
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
# Pry will output the content of the method from which it was called.
|
128
|
+
# This small method exists solely to reduce the amount of pry's output
|
129
|
+
# that is not needed here.
|
130
|
+
def run_pry
|
131
|
+
binding.pry
|
132
|
+
|
133
|
+
# the seemingly useless line below is needed to avoid pry's exiting
|
134
|
+
# (see https://github.com/deivid-rodriguez/pry-byebug/issues/45)
|
135
|
+
_a = nil
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
# Runs a pry session in the context of this object.
|
140
|
+
# Commands and options specified on the command line can also be specified in the shell.
|
141
|
+
def run_shell
|
142
|
+
begin
|
143
|
+
require 'pry'
|
144
|
+
rescue LoadError
|
145
|
+
message = "The 'pry' gem and/or one of its prerequisites, required for running the shell, was not found." +
|
146
|
+
" Please `gem install pry` or, if necessary, `sudo gem install pry`."
|
147
|
+
raise Error.new(message)
|
148
|
+
end
|
149
|
+
|
150
|
+
print_help
|
151
|
+
|
152
|
+
# Enable the line below if you have any problems with pry configuration being loaded
|
153
|
+
# that is messing up this runtime use of pry:
|
154
|
+
# Pry.config.should_load_rc = false
|
155
|
+
|
156
|
+
# Strangely, this is the only thing I have found that successfully suppresses the
|
157
|
+
# code context output, which is not useful here. Anyway, this will differentiate
|
158
|
+
# a pry command from a DSL command, which _is_ useful here.
|
159
|
+
Pry.config.command_prefix = '%'
|
160
|
+
|
161
|
+
run_pry
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
# Look up the command name and, if found, run it. If not, execute the passed block.
|
166
|
+
def attempt_command_action(command, *args, &error_handler_block)
|
167
|
+
no_command_specified = command.nil?
|
168
|
+
command = 'help' if no_command_specified
|
169
|
+
|
170
|
+
action = find_command_action(command)
|
171
|
+
result = nil
|
172
|
+
|
173
|
+
if action
|
174
|
+
result = action.(*args)
|
175
|
+
else
|
176
|
+
error_handler_block.call
|
177
|
+
nil
|
178
|
+
end
|
179
|
+
|
180
|
+
if no_command_specified
|
181
|
+
puts enclose_in_hyphen_lines('! No operations specified !')
|
182
|
+
end
|
183
|
+
result
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
# For use by the shell when the user types the DSL commands
|
188
|
+
def method_missing(method_name, *method_args)
|
189
|
+
attempt_command_action(method_name.to_s, *method_args) do
|
190
|
+
puts(%Q{"#{method_name}" is not a valid command or option. } \
|
191
|
+
<< 'If you intend for this to be a string literal, ' \
|
192
|
+
<< 'use quotes or %q{}/%Q{}.')
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
# Processes the command (ARGV[0]) and any relevant options (ARGV[1..-1]).
|
198
|
+
#
|
199
|
+
# CAUTION! In interactive mode, any strings entered (e.g. a network name) MUST
|
200
|
+
# be in a form that the Ruby interpreter will recognize as a string,
|
201
|
+
# i.e. single or double quotes, %q, %Q, etc.
|
202
|
+
# Otherwise it will assume it's a method name and pass it to method_missing!
|
203
|
+
def process_command_line
|
204
|
+
attempt_command_action(ARGV[0], *ARGV[1..-1]) do
|
205
|
+
print_help
|
206
|
+
raise BadCommandError.new(
|
207
|
+
%Q{! Unrecognized command. Command was #{ARGV.first.inspect} and options were #{ARGV[1..-1].inspect}.})
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
def quit
|
213
|
+
if interactive_mode
|
214
|
+
exit(0)
|
215
|
+
else
|
216
|
+
puts "This command can only be run in shell mode."
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
def cmd_c
|
222
|
+
puts chart_of_accounts.report_string
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
def cmd_h
|
227
|
+
print_help
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
def cmd_j
|
232
|
+
journal_names = book_set.journals.map(&:short_name)
|
233
|
+
interactive_mode ? journal_names : ap(journal_names)
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
def book_set
|
238
|
+
@book_set ||= load_data
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
def load_data
|
243
|
+
@book_set = BookSetLoader.load(run_options)
|
244
|
+
end
|
245
|
+
alias_method :reload_data, :load_data
|
246
|
+
|
247
|
+
|
248
|
+
def cmd_rel
|
249
|
+
reload_data
|
250
|
+
nil
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
# All reports as Ruby objects; only makes sense in shell mode.
|
255
|
+
def cmd_rep
|
256
|
+
unless run_options.interactive_mode
|
257
|
+
raise Error.new("Option 'all_reports' is only available in shell mode. Try 'display_reports' or 'write_reports'.")
|
258
|
+
end
|
259
|
+
|
260
|
+
os = OpenStruct.new(book_set.all_reports($filter))
|
261
|
+
def os.keys; to_h.keys.map(&:to_s); end # add hash methods for convenience
|
262
|
+
def os.values; to_h.values; end
|
263
|
+
def os.at(index); self.public_send(keys[index]); end # to access as array, e.g. `a.at(1)`
|
264
|
+
os
|
265
|
+
end
|
266
|
+
|
267
|
+
|
268
|
+
def cmd_d
|
269
|
+
book_set.all_reports($filter).each do |short_name, report_text|
|
270
|
+
puts "#{short_name}:\n\n"
|
271
|
+
puts report_text
|
272
|
+
puts "\n\n\n"
|
273
|
+
end
|
274
|
+
nil
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
def cmd_proj
|
279
|
+
`open https://github.com/keithrbennett/rock_books`
|
280
|
+
end
|
281
|
+
|
282
|
+
|
283
|
+
def cmd_rec(options)
|
284
|
+
unless run_options.do_receipts
|
285
|
+
raise Error.new("Receipt processing was requested but has been disabled with --no-receipts.")
|
286
|
+
end
|
287
|
+
|
288
|
+
missing, existing = book_set.missing_and_existing_receipts
|
289
|
+
|
290
|
+
print_missing = -> { puts "Missing Receipts:"; ap missing }
|
291
|
+
print_existing = -> { puts "Existing Receipts:"; ap existing }
|
292
|
+
|
293
|
+
case options.first.to_s
|
294
|
+
when 'a' # all
|
295
|
+
if run_options.interactive_mode
|
296
|
+
{ missing: missing, existing: existing }
|
297
|
+
else
|
298
|
+
print_missing.(); print_existing.()
|
299
|
+
end
|
300
|
+
|
301
|
+
when 'm'
|
302
|
+
run_options.interactive_mode ? missing : print_missing.()
|
303
|
+
|
304
|
+
when 'e'
|
305
|
+
run_options.interactive_mode ? existing : print_existing.()
|
306
|
+
|
307
|
+
else
|
308
|
+
message = "Invalid option for receipts. Must be 'a' for all, 'm' for missing, or 'e' for existing."
|
309
|
+
if run_options.interactive_mode
|
310
|
+
puts message
|
311
|
+
else
|
312
|
+
raise Error.new(message)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def cmd_w
|
318
|
+
book_set.all_reports_to_files(run_options.output_dir, $filter)
|
319
|
+
nil
|
320
|
+
end
|
321
|
+
|
322
|
+
|
323
|
+
def cmd_x
|
324
|
+
quit
|
325
|
+
end
|
326
|
+
|
327
|
+
|
328
|
+
def commands
|
329
|
+
@commands_ ||= [
|
330
|
+
Command.new('rec', 'receipts', -> (*options) { cmd_rec(options) }),
|
331
|
+
Command.new('rep', 'reports', -> (*_options) { cmd_rep }),
|
332
|
+
Command.new('d', 'display_reports', -> (*_options) { cmd_d }),
|
333
|
+
Command.new('w', 'write_reports', -> (*_options) { cmd_w }),
|
334
|
+
Command.new('c', 'chart_of_accounts', -> (*_options) { cmd_c }),
|
335
|
+
Command.new('jo', 'journals', -> (*_options) { cmd_j }),
|
336
|
+
Command.new('h', 'help', -> (*_options) { cmd_h }),
|
337
|
+
Command.new('proj','project_page', -> (*_options) { cmd_proj }),
|
338
|
+
Command.new('q', 'quit', -> (*_options) { cmd_x }),
|
339
|
+
Command.new('rel', 'reload_data', -> (*_options) { cmd_rel }),
|
340
|
+
Command.new('x', 'xit', -> (*_options) { cmd_x })
|
341
|
+
]
|
342
|
+
end
|
343
|
+
|
344
|
+
|
345
|
+
def find_command_action(command_string)
|
346
|
+
result = commands.detect do |cmd|
|
347
|
+
cmd.max_string.start_with?(command_string) \
|
348
|
+
&& \
|
349
|
+
command_string.length >= cmd.min_string.length # e.g. 'c' by itself should not work
|
350
|
+
end
|
351
|
+
result ? result.action : nil
|
352
|
+
end
|
353
|
+
|
354
|
+
|
355
|
+
# If a post-processor has been configured (e.g. YAML or JSON), use it.
|
356
|
+
def post_process(object)
|
357
|
+
post_processor ? post_processor.(object) : object
|
358
|
+
end
|
359
|
+
|
360
|
+
|
361
|
+
def post_processor
|
362
|
+
run_options.post_processor
|
363
|
+
end
|
364
|
+
|
365
|
+
|
366
|
+
# Convenience Method(s)
|
367
|
+
|
368
|
+
# Easier than remembering and typing Date.iso8601.
|
369
|
+
def td(date_string)
|
370
|
+
Date.iso8601(date_string)
|
371
|
+
end
|
372
|
+
|
373
|
+
|
374
|
+
def call
|
375
|
+
begin
|
376
|
+
# By this time, the Main class has removed the command line options, and all that is left
|
377
|
+
# in ARGV is the commands and their options.
|
378
|
+
if @interactive_mode
|
379
|
+
run_shell
|
380
|
+
else
|
381
|
+
process_command_line
|
382
|
+
end
|
383
|
+
|
384
|
+
rescue BadCommandError => error
|
385
|
+
separator_line = "! #{'-' * 75} !\n"
|
386
|
+
puts '' << separator_line << error.to_s << "\n" << separator_line
|
387
|
+
exit(-1)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|