rock_books 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7462246cd8ae62e943c75905486e1732586bd2d9c746c5ea145bd3c751cf637
4
- data.tar.gz: 78fa22e6f1186a552d453340d2ac052e342f959ea1fb58c9a8ed6fc0ea4ed37b
3
+ metadata.gz: 6a7c5d56c718ce58a0fdf060e25be5d3aaf5438ece48a76e02f912e09d203c39
4
+ data.tar.gz: b467740538a5e89504fc7e2ac6b72190181519471f898a5c26ae7cfa29a53496
5
5
  SHA512:
6
- metadata.gz: b24be35a05935f8ae333f656d6400523a43a8f118b54cb0d42ecdc64bc405ac1ee5543d4292deef13676f898597ff6548142beb5089dc041fddd3e06bdceb6a0
7
- data.tar.gz: 458d376cbad3a5d420c3908c6eb8a33037539b6d43d28f50ca5f55327d76edcd85b4d243a975fe72efdda40e0ea96b64570d3e3e551ef8678e354be0d05e4232
6
+ metadata.gz: b43529ffa215e13375538d173b939baf659b814dc07a85e7f6c9a695449a86a8dfe49d5134bca22b0c7a479d0707221c4d9bd87efdcef4ac9df95a815b148ca0
7
+ data.tar.gz: 15776062f22eeb7ad746c303289512c9f81005f04f87518c1738d3923991d45a9ef32951b930946d2c746b6b7a6a52516892f4a8cbf5811ca2d58da4fadff2d5
data/README.md CHANGED
@@ -31,6 +31,11 @@ To simplify its implementation, RockBooks assumes some conventions:
31
31
  * `statements` - statements from banks, etc.
32
32
  * `worksheets` - spreadsheets, etc., e.g. mileage and per diem calculations
33
33
 
34
+ #### Supported Operating Systems
35
+
36
+ At this time, RockBooks is supported only on Mac OS and Linux. (It's really the external tools that is the issue, not the Ruby code.) It should probably work with Windows Subsystem for Linux (WSL) but it hasn't been tested. If you're unsuccessful trying to use RockBooks with WSL, give me as much information as possible and I'll try to resolve the issue.
37
+
38
+ If you get an error message saying that an external command is missing, install that command using your system's package manager (e.g. `sudo apt install txt2html`).
34
39
 
35
40
  #### Text Files as Input
36
41
 
@@ -91,7 +96,6 @@ As a product written by a single developer in his spare time, RockBooks lacks so
91
96
  * On the fly data validation
92
97
  * Data entry conveniences such as drop down selection lists for data such as accounts
93
98
  * Fancy reporting and graphing -- however, RockBooks' bringing links to all the entity's documentation and output into a single web page may well be more useful
94
- * At this time, RockBooks is only tested on Macs. The input files are plain text files and could be created on any OS, but the validation and report generation might not work. Get in touch with me if you are using a different OS and want to use RockBooks, and are willing and available to test my changes. Linux in particular should be an easy port.
95
99
 
96
100
 
97
101
  ## Installation
@@ -1,3 +1,7 @@
1
+ ### v0.6.0
2
+
3
+ * Linux support added!
4
+
1
5
  ### v0.5.0
2
6
 
3
7
  * Add receipt hyperlinks to HTML output.
@@ -358,7 +358,7 @@ When in interactive shell mode:
358
358
  end
359
359
 
360
360
  def cmd_w
361
- book_set.all_reports_to_files(run_options.output_dir, $filter)
361
+ BookSetReporter.new(book_set, run_options.output_dir, $filter).call
362
362
  nil
363
363
  end
364
364
 
@@ -1,18 +1,12 @@
1
1
  require 'awesome_print'
2
+ require 'os'
2
3
 
3
4
  require_relative 'chart_of_accounts'
4
5
  require_relative 'journal'
5
6
  require_relative '../filters/journal_entry_filters' # for shell mode
6
7
  require_relative '../helpers/html_helper'
7
8
  require_relative '../helpers/parse_helper'
8
- require_relative '../reports/balance_sheet'
9
- require_relative '../reports/income_statement'
10
- require_relative '../reports/multidoc_transaction_report'
11
- require_relative '../reports/receipts_report'
12
- require_relative '../reports/report_context'
13
- require_relative '../reports/transaction_report'
14
- require_relative '../reports/tx_by_account'
15
- require_relative '../reports/tx_one_account'
9
+ require_relative '../reports/book_set_reporter'
16
10
 
17
11
  require 'erb'
18
12
  require 'open3'
@@ -29,107 +23,6 @@ module RockBooks
29
23
  end
30
24
 
31
25
 
32
- def report_context
33
- @report_context ||= ReportContext.new(chart_of_accounts, journals, 80)
34
- end
35
-
36
-
37
- def all_reports(filter = nil)
38
-
39
- context = report_context
40
- report_hash = context.journals.each_with_object({}) do |journal, report_hash|
41
- key = journal.short_name.to_sym
42
- report_hash[key] = TransactionReport.new(journal, context).call(filter)
43
- end
44
- report_hash[:all_txns_by_date] = MultidocTransactionReport.new(context).call(filter)
45
- report_hash[:all_txns_by_amount] = MultidocTransactionReport.new(context).call(filter, :amount)
46
- report_hash[:all_txns_by_acct] = TxByAccount.new(context).call
47
- report_hash[:balance_sheet] = BalanceSheet.new(context).call
48
- report_hash[:income_statement] = IncomeStatement.new(context).call
49
-
50
- if run_options.do_receipts
51
- report_hash[:receipts] = ReceiptsReport.new(context, *missing_existing_unused_receipts).call
52
- end
53
-
54
- chart_of_accounts.accounts.each do |account|
55
- key = ('acct_' + account.code).to_sym
56
- report = TxOneAccount.new(context, account.code).call
57
- report_hash[key] = report
58
- end
59
-
60
- report_hash
61
- end
62
-
63
-
64
- def run_command(command)
65
- puts "\n----\nRunning command: #{command}"
66
- stdout, stderr, status = Open3.capture3(command)
67
- puts "Status was #{status}."
68
- unless stdout.size == 0
69
- puts "\nStdout was:\n\n#{stdout}"
70
- end
71
- unless stderr.size == 0
72
- puts "\nStderr was:\n\n#{stderr}"
73
- end
74
- puts
75
- stdout
76
- end
77
-
78
-
79
- def all_reports_to_files(directory = '.', filter = nil)
80
- reports = all_reports(filter)
81
-
82
- create_directories = -> do
83
- %w(txt pdf html).each do |format|
84
- dir = File.join(directory, format, SINGLE_ACCT_SUBDIR)
85
- FileUtils.mkdir_p(dir)
86
- end
87
- end
88
-
89
- # "./pdf/short_name.pdf" or "./pdf/single_account/short_name.pdf"
90
- build_filespec = ->(directory, short_name, file_format) do
91
- fragments = [directory, file_format, "#{short_name}.#{file_format}"]
92
- is_acct_report = /^acct_/.match(short_name)
93
- if is_acct_report
94
- fragments.insert(2, SINGLE_ACCT_SUBDIR)
95
- end
96
- File.join(*fragments)
97
- end
98
-
99
- create_index_html = -> do
100
- filespec = build_filespec.(directory, 'index', 'html')
101
- File.write(filespec, index_html_content)
102
- puts "Created index.html"
103
- end
104
-
105
- write_reports = ->do
106
-
107
- reports.each do |short_name, report_text|
108
- txt_filespec = build_filespec.(directory, short_name, 'txt')
109
- html_filespec = build_filespec.(directory, short_name, 'html')
110
- pdf_filespec = build_filespec.(directory, short_name, 'pdf')
111
-
112
- File.write(txt_filespec, report_text)
113
- # Use smaller size for the PDF but larger size for the web pages:
114
- run_command("textutil -convert html -font 'Courier New Bold' -fontsize 11 #{txt_filespec} -output #{html_filespec}")
115
- run_command("cupsfilter #{html_filespec} > #{pdf_filespec}")
116
- run_command("textutil -convert html -font 'Courier New Bold' -fontsize 14 #{txt_filespec} -output #{html_filespec}")
117
-
118
- hyperlinkized_text, replacements_made = HtmlHelper.convert_receipts_to_hyperlinks(File.read(html_filespec))
119
- if replacements_made
120
- File.write(html_filespec, hyperlinkized_text)
121
- end
122
-
123
- puts "Created reports in txt, html, and pdf for #{"%-20s" % short_name} at #{File.dirname(txt_filespec)}.\n\n\n"
124
- end
125
- end
126
-
127
- create_directories.()
128
- create_index_html.()
129
- write_reports.()
130
- end
131
-
132
-
133
26
  def journal_names
134
27
  journals.map(&:short_name)
135
28
  end
@@ -146,37 +39,6 @@ module RockBooks
146
39
  @all_entries ||= Journal.entries_in_documents(journals)
147
40
  end
148
41
 
149
-
150
- def receipt_full_filespec(receipt_filespec)
151
- File.join(run_options.receipt_dir, receipt_filespec)
152
- end
153
-
154
-
155
- def missing_existing_unused_receipts
156
- missing = []
157
- existing = []
158
- unused = Dir['receipts/**/*'].select { |s| File.file?(s) }.sort # Remove files as they are found
159
- unused.map! { |s| "./" + s } # Prepend './' to match the data
160
-
161
- all_entries.each do |entry|
162
- entry.receipts.each do |receipt|
163
- filespec = receipt_full_filespec(receipt)
164
- unused.delete(filespec)
165
- file_exists = File.file?(filespec)
166
- list = (file_exists ? existing : missing)
167
- list << { receipt: receipt, journal: entry.doc_short_name }
168
- end
169
- end
170
- [missing, existing, unused]
171
- end
172
-
173
- def index_html_content
174
- erb_filespec = File.join(File.dirname(__FILE__), 'index.html.erb')
175
- erb = ERB.new(File.read(erb_filespec))
176
- erb.result_with_hash(
177
- journals: journals,
178
- chart_of_accounts: chart_of_accounts,
179
- run_options: run_options)
180
- end
181
42
  end
182
43
  end
44
+
@@ -0,0 +1,201 @@
1
+ require_relative '../documents/book_set'
2
+
3
+ require_relative 'balance_sheet'
4
+ require_relative 'income_statement'
5
+ require_relative 'multidoc_transaction_report'
6
+ require_relative 'receipts_report'
7
+ require_relative 'report_context'
8
+ require_relative 'transaction_report'
9
+ require_relative 'tx_by_account'
10
+ require_relative 'tx_one_account'
11
+
12
+ module RockBooks
13
+ class BookSetReporter
14
+
15
+ extend Forwardable
16
+
17
+ attr_reader :book_set, :output_dir, :filter, :context
18
+
19
+ def_delegator :book_set, :all_entries
20
+ def_delegator :book_set, :journals
21
+ def_delegator :book_set, :chart_of_accounts
22
+ def_delegator :book_set, :run_options
23
+
24
+
25
+ def initialize(book_set, output_dir, filter = nil)
26
+ @book_set = book_set
27
+ @output_dir = output_dir
28
+ @filter = filter
29
+ @context = ReportContext.new(book_set.chart_of_accounts, book_set.journals, 80)
30
+ end
31
+
32
+
33
+ def call
34
+ check_prequisite_executables
35
+ reports = all_reports(filter)
36
+ create_directories
37
+ create_index_html
38
+ write_reports(reports)
39
+ end
40
+
41
+
42
+ # All methods after this point are private.
43
+
44
+ private def all_reports(filter = nil)
45
+
46
+ report_hash = journals.each_with_object({}) do |journal, report_hash|
47
+ key = journal.short_name.to_sym
48
+ report_hash[key] = TransactionReport.new(journal, context).call(filter)
49
+ end
50
+
51
+ report_hash[:all_txns_by_date] = MultidocTransactionReport.new(context).call(filter)
52
+ report_hash[:all_txns_by_amount] = MultidocTransactionReport.new(context).call(filter, :amount)
53
+ report_hash[:all_txns_by_acct] = TxByAccount.new(context).call
54
+ report_hash[:balance_sheet] = BalanceSheet.new(context).call
55
+ report_hash[:income_statement] = IncomeStatement.new(context).call
56
+
57
+ if run_options.do_receipts
58
+ report_hash[:receipts] = ReceiptsReport.new(context, *missing_existing_unused_receipts).call
59
+ end
60
+
61
+ chart_of_accounts.accounts.each do |account|
62
+ key = ('acct_' + account.code).to_sym
63
+ report = TxOneAccount.new(context, account.code).call
64
+ report_hash[key] = report
65
+ end
66
+
67
+ report_hash
68
+ end
69
+
70
+
71
+ private def run_command(command)
72
+ puts "\n----\nRunning command: #{command}"
73
+ stdout, stderr, status = Open3.capture3(command)
74
+ puts "Status was #{status}."
75
+ unless stdout.size == 0
76
+ puts "\nStdout was:\n\n#{stdout}"
77
+ end
78
+ unless stderr.size == 0
79
+ puts "\nStderr was:\n\n#{stderr}"
80
+ end
81
+ puts
82
+ stdout
83
+ end
84
+
85
+
86
+ private def executable_exists?(name)
87
+ `which #{name}`
88
+ $?.success?
89
+ end
90
+
91
+
92
+ private def check_prequisite_executables
93
+ raise "Report generation is not currently supported in Windows." if OS.windows?
94
+ required_exes = OS.mac? ? %w(textutil cupsfilter) : %w(txt2html cupsfilter)
95
+ missing_exes = required_exes.reject { |exe| executable_exists?(exe) }
96
+ if missing_exes.any?
97
+ raise "Missing required report generation executable(s): #{missing_exes.join(', ')}"
98
+ end
99
+ end
100
+
101
+
102
+ private def create_directories
103
+ %w(txt pdf html).each do |format|
104
+ dir = File.join(output_dir, format, SINGLE_ACCT_SUBDIR)
105
+ FileUtils.mkdir_p(dir)
106
+ end
107
+ end
108
+
109
+
110
+ # "./pdf/short_name.pdf" or "./pdf/single_account/short_name.pdf"
111
+ private def build_filespec(directory, short_name, file_format)
112
+ fragments = [directory, file_format, "#{short_name}.#{file_format}"]
113
+ is_acct_report = /^acct_/.match(short_name)
114
+ if is_acct_report
115
+ fragments.insert(2, SINGLE_ACCT_SUBDIR)
116
+ end
117
+ File.join(*fragments)
118
+ end
119
+
120
+
121
+ private def create_index_html
122
+ filespec = build_filespec(output_dir, 'index', 'html')
123
+ File.write(filespec, index_html_content)
124
+ puts "Created index.html"
125
+ end
126
+
127
+
128
+ private def write_reports(reports)
129
+
130
+ reports.each do |short_name, report_text|
131
+ txt_filespec = build_filespec(output_dir, short_name, 'txt')
132
+ html_filespec = build_filespec(output_dir, short_name, 'html')
133
+ pdf_filespec = build_filespec(output_dir, short_name, 'pdf')
134
+
135
+ File.write(txt_filespec, report_text)
136
+
137
+ # Linux & Mac OS
138
+ cupsfilter = -> { run_command("cupsfilter #{txt_filespec} > #{pdf_filespec}") }
139
+
140
+ # Mac OS
141
+ textutil = ->(font_size) do
142
+ run_command("textutil -convert html -font 'Courier New Bold' -fontsize #{font_size} #{txt_filespec} -output #{html_filespec}")
143
+ end
144
+
145
+ # Linux
146
+ txt2html = -> { run_command("txt2html --preformat_trigger_lines 0 #{txt_filespec} > #{html_filespec}") }
147
+
148
+ # Use smaller size for the PDF but larger size for the web pages:
149
+ if OS.mac?
150
+ textutil.(11)
151
+ cupsfilter.()
152
+ textutil.(14)
153
+ else
154
+ txt2html.()
155
+ cupsfilter.()
156
+ end
157
+
158
+ hyperlinkized_text, replacements_made = HtmlHelper.convert_receipts_to_hyperlinks(File.read(html_filespec))
159
+ if replacements_made
160
+ File.write(html_filespec, hyperlinkized_text)
161
+ end
162
+
163
+ puts "Created reports in txt, html, and pdf for #{"%-20s" % short_name} at #{File.dirname(txt_filespec)}.\n\n\n"
164
+ end
165
+ end
166
+
167
+
168
+ private def receipt_full_filespec(receipt_filespec)
169
+ File.join(run_options.receipt_dir, receipt_filespec)
170
+ end
171
+
172
+
173
+ private def missing_existing_unused_receipts
174
+ missing = []
175
+ existing = []
176
+ unused = Dir['receipts/**/*'].select { |s| File.file?(s) }.sort # Remove files as they are found
177
+ unused.map! { |s| "./" + s } # Prepend './' to match the data
178
+
179
+ all_entries.each do |entry|
180
+ entry.receipts.each do |receipt|
181
+ filespec = receipt_full_filespec(receipt)
182
+ unused.delete(filespec)
183
+ file_exists = File.file?(filespec)
184
+ list = (file_exists ? existing : missing)
185
+ list << { receipt: receipt, journal: entry.doc_short_name }
186
+ end
187
+ end
188
+ [missing, existing, unused]
189
+ end
190
+
191
+
192
+ private def index_html_content
193
+ erb_filespec = File.join(File.dirname(__FILE__), 'index.html.erb')
194
+ erb = ERB.new(File.read(erb_filespec))
195
+ erb.result_with_hash(
196
+ journals: journals,
197
+ chart_of_accounts: chart_of_accounts,
198
+ run_options: run_options)
199
+ end
200
+ end
201
+ end
@@ -1,3 +1,3 @@
1
1
  module RockBooks
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -30,10 +30,11 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ["lib"]
32
32
 
33
- spec.add_dependency 'awesome_print', '> 0'
33
+ spec.add_dependency 'amazing_print', '> 0'
34
+ spec.add_dependency 'os', '> 1.0.0'
34
35
  spec.add_dependency 'pry', '> 0.0.0'
35
36
 
36
- spec.add_development_dependency "bundler", "~> 1.16"
37
- spec.add_development_dependency "rake", "~> 10.0"
37
+ spec.add_development_dependency "bundler", "~> 2.0"
38
+ spec.add_development_dependency "rake", ">= 12.3.3"
38
39
  spec.add_development_dependency "rspec", "~> 3.0"
39
40
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rock_books
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keith Bennett
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-02-02 00:00:00.000000000 Z
11
+ date: 2020-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: awesome_print
14
+ name: amazing_print
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">"
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: os
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: pry
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -44,28 +58,28 @@ dependencies:
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: '1.16'
61
+ version: '2.0'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: '1.16'
68
+ version: '2.0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rake
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
- - - "~>"
73
+ - - ">="
60
74
  - !ruby/object:Gem::Version
61
- version: '10.0'
75
+ version: 12.3.3
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
- - - "~>"
80
+ - - ">="
67
81
  - !ruby/object:Gem::Version
68
- version: '10.0'
82
+ version: 12.3.3
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rspec
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -105,11 +119,9 @@ files:
105
119
  - lib/rock_books/cmd_line/main.rb
106
120
  - lib/rock_books/documents/book_set.rb
107
121
  - lib/rock_books/documents/chart_of_accounts.rb
108
- - lib/rock_books/documents/index.html.erb
109
122
  - lib/rock_books/documents/journal.rb
110
123
  - lib/rock_books/documents/journal_entry.rb
111
124
  - lib/rock_books/documents/journal_entry_builder.rb
112
- - lib/rock_books/documents/receipts.html.erb
113
125
  - lib/rock_books/errors/account_not_found_error.rb
114
126
  - lib/rock_books/errors/date_range_error.rb
115
127
  - lib/rock_books/errors/error.rb
@@ -121,8 +133,11 @@ files:
121
133
  - lib/rock_books/helpers/html_helper.rb
122
134
  - lib/rock_books/helpers/parse_helper.rb
123
135
  - lib/rock_books/reports/balance_sheet.rb
136
+ - lib/rock_books/reports/book_set_reporter.rb
124
137
  - lib/rock_books/reports/income_statement.rb
138
+ - lib/rock_books/reports/index.html.erb
125
139
  - lib/rock_books/reports/multidoc_transaction_report.rb
140
+ - lib/rock_books/reports/receipts.html.erb
126
141
  - lib/rock_books/reports/receipts_report.rb
127
142
  - lib/rock_books/reports/report_context.rb
128
143
  - lib/rock_books/reports/reporter.rb
@@ -416,7 +431,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
416
431
  - !ruby/object:Gem::Version
417
432
  version: '0'
418
433
  requirements: []
419
- rubygems_version: 3.0.6
434
+ rubygems_version: 3.1.4
420
435
  signing_key:
421
436
  specification_version: 4
422
437
  summary: Very basic accounting package.