salestax 0.0.1 → 0.0.2

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
  SHA1:
3
- metadata.gz: a0fd7c247bcbb8a37acc5defcc9a57b095a9f0b4
4
- data.tar.gz: c2741cf4cbaa3dffbe9f1f2367aa5ce1fe45fe40
3
+ metadata.gz: c8b0453465806dd4fc525a7f0488247d479005b6
4
+ data.tar.gz: d2749e2f79a6b87b351ffd412a14f4894218b710
5
5
  SHA512:
6
- metadata.gz: 6bb6e03211201492c8f448b7e9094e5c50e1863f5dacfb30c2e06294bc961d22a4364c9e6d7f432d6ecb22e0317a0e7171ec8a44208580b84ab11b81bc3b00b6
7
- data.tar.gz: 11346abbfd2b1a2127ea88e70cb928802ee7aeabaa1ce64c4b50826cdef649efdd54447cc8f89a7f017d690efd442e22960ba78863fb040099a9ef094cb23b40
6
+ metadata.gz: e4b6272bbc957e80a9695ad788bf30b6471e44140c258bea00a4fd782d82f4ae1b85423e4ea75382d933f0b159f7fa0cf6a43fd61672300df95f61a20035699d
7
+ data.tar.gz: afb0ea18e520db5e7e92565f0849b71aea83d5f5d93ef42d998b58a1804595c31b078a0707c4a444e39d54c62657644e0e84b5a9c6f2500f3fba578ea266e681
data/.gitignore CHANGED
@@ -0,0 +1 @@
1
+ .gem
@@ -0,0 +1,27 @@
1
+ ## How to contribute
2
+
3
+ Fork, then clone the repo:
4
+
5
+ git clone git@github.com:your-username/sales-tax.git
6
+
7
+ Check you ruby and development dependencies versions:
8
+
9
+ $ ruby -v
10
+
11
+ $ gem list
12
+
13
+ Make sure the tests pass:
14
+
15
+ rspec
16
+
17
+ Make your change. Add tests for your change. Make the tests pass:
18
+
19
+ rspec
20
+
21
+ Push to your fork and submit a pull request. Some things that are equal parts necessary/epic:
22
+
23
+ * Write tests.
24
+
25
+ * Follow a sensible style guide. My pick is [this one](https://github.com/bbatsov/ruby-style-guide).
26
+
27
+ * Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org>
data/README.md CHANGED
@@ -3,15 +3,135 @@ Sales Tax
3
3
 
4
4
  A basic sales tax calculator.
5
5
 
6
- ## Description
6
+ ## Installation
7
7
 
8
- Basic sales tax is applicable at a rate of 10% on all goods, except books, food, and medical products that are exempt. Import duty is an additional sales tax applicable on all imported goods at a rate of 5%, with no exemptions.
8
+ via RubyGems:
9
+
10
+ $ gem install salestax
11
+
12
+
13
+ ## Usage
14
+
15
+ Pipe in a file with items:
16
+
17
+ $ salestax < path/to/file
18
+
19
+ An example file with items:
20
+
21
+ 1, book, 12.49
22
+ 1, music CD, 14.99
23
+ 1, chocolate bar, 0.85
24
+
25
+ To which the calculator will print:
26
+
27
+ 1, book, 12.49
28
+ 1, music CD, 16.49
29
+ 1, chocolate bar, 0.85
30
+
31
+ Sales Taxes: 1.50
32
+ Total: 29.83
33
+
34
+ More example files can be found at `data/`
35
+
36
+ ## Dependencies
37
+
38
+ ruby version ~> 2.1.0p0
39
+
40
+ To check your version run:
41
+
42
+ $ ruby -v
43
+
44
+ To learn how to install ruby visit [ruby-lang.org/en/installation/](https://www.ruby-lang.org/en/installation/)
45
+
46
+
47
+ ## Troubleshooting
48
+
49
+ ### Development environment
50
+
51
+ * OSX 10.8.5, ruby 2.1.2p95
52
+
53
+ Development dependencies:
54
+
55
+ rspec ~> 3.1
56
+
57
+ To install them along the gem:
58
+
59
+ $ gem install --dev salestax
60
+
61
+ ### Compatible environments
62
+
63
+ * Ubuntu 12.04 x32, ruby 2.1.0p0
64
+
65
+ ### Incompatible environments
66
+
67
+ * ruby < 2.1.0
68
+
69
+ ### Tests
70
+
71
+ To run the specs:
72
+
73
+ $ rspec
74
+
75
+ To run just the integration tests:
76
+
77
+ $ rspec spec/features
78
+
79
+ ## Overview
80
+
81
+ The application is designed to read items from `$stdin` and print the receipt to `$stdout`.
82
+
83
+ ### Input Format
84
+
85
+ Items feed into the program are expected to be comma-separated values with the following format:
86
+
87
+ <quantity>, <description>, <unit_price>
88
+
89
+ Example:
90
+
91
+ 1, book, 12.49
92
+
93
+ Please note the blank space between the commas and `<description>` / `<unit_price>`, since it is required.
94
+
95
+ Each row in the input represents a line item of the receipt.
96
+
97
+ #### Quantity
98
+
99
+ Quantity is expected to be a whole value, thus only digits are allowed.
100
+
101
+ #### Description
102
+
103
+ Anything but a comma character is allowed in the description. They must contain at least one word character (letter, number, underscore).
104
+
105
+ * Imported items should contain 'imported' in the description.
106
+
107
+ * Food items should contain 'chocolate' in the description.
108
+
109
+ * Medicine items should contain 'headache' in the description.
110
+
111
+ * Book items should contain 'book' in the description.
9
112
 
10
- When I purchase items I receive a receipt that lists the name of all the items and their price (including tax), finishing with the total cost of the items, and the total amounts of sales taxes paid. The rounding rules for sales tax are that for a tax rate of n%, a shelf price of p contains (np/100 rounded up to the nearest 0.05) amount of sales tax.
113
+ #### Unit price
11
114
 
12
- Write an application that prints out the receipt details for these shopping carts; this application should be written in Ruby [1] and use Rspec [2] to test inputs and the expected outputs. The output should be to standard out or CSV.
115
+ Unit price is expected to be a decimal value, though it can be a whole one as well. A '.' is expected as the fractional separator.
13
116
 
14
- Proper object orientated design is important. Each row in the input represents a line item of the receipt.
117
+ ### Output Format
118
+
119
+ A receipt will be printed that lists the name of all the items and their price (including tax), finishing with the total cost of the items, and the total amounts of sales taxes paid.
120
+
121
+ Example:
122
+
123
+ 1, book, 12.49
124
+ 1, music CD, 16.49
125
+ 1, chocolate bar, 0.85
126
+
127
+ Sales Taxes: 1.50
128
+ Total: 29.83
129
+
130
+ ### Taxing rules
131
+
132
+ Basic sales tax is applicable at a rate of 10% on all goods, except books, food, and medical products that are exempt. Import duty is an additional sales tax applicable on all imported goods at a rate of 5%, with no exemptions.
133
+
134
+ The rounding rules for sales tax are that for a tax rate of n%, a shelf price of p, it contains np/100 (rounded up to the nearest 0.05) amount of sales tax.
15
135
 
16
136
  ### Examples
17
137
 
@@ -65,3 +185,150 @@ Output:
65
185
 
66
186
  Sales Taxes: 6.70
67
187
  Total: 74.68
188
+
189
+ ## Design
190
+
191
+ The application flows in the following way:
192
+
193
+ #### 1 Launch
194
+
195
+ $ salestax < path/to/file
196
+
197
+ ##### 1.1 bin/salestax
198
+
199
+ The `salestax` in the command is a ruby executable in your load path. This executable contains the following lines:
200
+
201
+ ```ruby
202
+ require 'sales_tax'
203
+ SalesTax::Application.new.run
204
+ ```
205
+
206
+ The first line requires the file `./lib/sales_tax.rb` which loads the library. The second line creates and instance of the application.
207
+
208
+ ##### 1.2 lib/sales_tax/application.rb
209
+
210
+ The `#run` method reads in items from the input source (defaults to `$stdin`) and passes them to the parser. The parser returns (might) a structured item which is added to the receipt.
211
+
212
+ ```ruby
213
+ def run
214
+ loop do
215
+ raw_input = input.gets
216
+ break unless raw_input
217
+ raw_input.chomp!
218
+ line_item = parser.parse(raw_input)
219
+ receipt.add_item(line_item) if line_item
220
+ end
221
+ puts receipt.print
222
+ end
223
+ ```
224
+
225
+ ##### 1.2.1 lib/sales_tax/line_item/parser.rb
226
+
227
+ `parser.parse()` first calls a `@input_validator` function which checks the formating of the input. It the pre-process the input into a basic hash composed of the quantity, description and price of the item. It finally delegates the heavy lifting to the augmenter function which is in charge of extending the hash with actual taxing info.
228
+
229
+ ```ruby
230
+ def parse(_in)
231
+ @input_validator.call(_in)
232
+ @augmenter.call(@pre_processor.call(_in))
233
+ end
234
+ ```
235
+
236
+ The `@augmenter` function delegates the actual augmenting feature to the augmenters, which in this case are hard-coded. The augmenters need to respond to the `#augment` message and either return the augmented hash or `nil` if they do not understand it.
237
+
238
+ ```ruby
239
+ @augmenter = lambda do |_in|
240
+ augmented = nil
241
+ [LineItem::Book, LineItem::Medicine, LineItem::Food].each do |augmenter|
242
+ augmented = augmenter.augment(_in)
243
+ break if augmented
244
+ end
245
+ augmented || LineItem::Base.augment(_in)
246
+ end
247
+ ```
248
+
249
+ This function will return the first augmented hash it encounters or whatever `LineItem::Base.augment(_in)` returns.
250
+
251
+ ##### 1.2.1.1 lib/sales_tax/line_item/base.rb
252
+
253
+ ```ruby
254
+ def self.augment(attributes)
255
+ new(attributes).to_hash if attributes[:description] =~ description_matcher
256
+ end
257
+ ```
258
+
259
+ This is the base method which `LineItem::(Food/Book/Medicine)` inherit. It tries to recognize the description in the hash, and if successful it will augment it.
260
+
261
+ Augmenting it will actually involve a long series of messages, which I will not cover here. All of them are contained within `lib/sales_tax/line_item/base.rb` and `lib/sales_tax/accountable.rb`
262
+
263
+ #### 1.2.2 lib/sales_tax/receipt.rb
264
+
265
+ Next line in `Application#run` is:
266
+
267
+ ```ruby
268
+ receipt.add_item(line_item) if line_item
269
+ ```
270
+
271
+ This simply adds the `line_item`, which is actually a hash, to a @items array in the receipt.
272
+
273
+ ```ruby
274
+ def add_item(item)
275
+ items << item
276
+ end
277
+ ```
278
+
279
+ This will be done for every line item. After all items have been added, the application prints the receipt:
280
+
281
+ ```ruby
282
+ puts receipt.print
283
+ ```
284
+
285
+ Printing will return a string with all the items printed out, as well as totals and sales tax totals.
286
+
287
+ ```ruby
288
+ def print
289
+ _print = ''
290
+ _print << print_items
291
+ _print << "\n"
292
+ _print << print_totals
293
+ _print
294
+ end
295
+ ```
296
+
297
+ #### 2 Exit
298
+
299
+ After the receipt is printed, the application exits.
300
+
301
+
302
+ ## Discussion
303
+
304
+ My main design decisions / concerns in no specific order are:
305
+
306
+ * `Accountable` depends on a `Taxable` role which only has a private interface. This is weird. A object should not depend on a private interface. The alternative here is to make that interface public (`#category`, `#imported?`, `#unit_price_str`), but that would make the `LineItem::Base` class to expose much more than I like. Another possible solution would be to make `Accountable` a class instead of a module, so that these messages can be passed as arguments. I liked the module approach better, which ended up bitting.
307
+
308
+ * There is a pending test for `LineItem::Parser.parse` which should test that the method delegates the augmentation part of it. This behavior is only covered by the integration test. Implementing this test would mean having the dependencies injected into the parser, which is better and that is why the pending test is left there.
309
+
310
+ * `Receipt#print` loops two times through the `@items` array. One for printing the items, the second for calculating the totals. I found this to be more readable since it allowed me to break the method into sub-routines.
311
+
312
+ * The quantity property of items is ignored since there is not enough information in the spec to decide what should be done with it. There is a [issue hosted on github](https://github.com/matiasanaya/sales-tax/issues/1) for this.
313
+
314
+ * I found input inconsistencies which I did not know how to manage. The decision was to re-format the input and open [another issue](https://github.com/matiasanaya/sales-tax/issues/2)
315
+
316
+ ### Subtleties
317
+
318
+ In `spec/shared_examples_for_line_item_base.rb` the following line:
319
+
320
+ ```
321
+ it { expect(described_class).to respond_to :augment }
322
+ ```
323
+ prints:
324
+
325
+ SalesTax::LineItem::Base
326
+ behaves like a line item
327
+ the public interface
328
+ should respond to #augment
329
+
330
+ This should read `should respond to .augment` since it is a method on the class.
331
+
332
+ ## Contributing
333
+
334
+ View [CONTRIBUTING.md](https://github.com/matiasanaya/sales-tax/blob/master/CONTRIBUTING.md)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../lib/sales_tax'
3
+ require 'sales_tax'
4
4
 
5
5
  SalesTax::Application.new.run
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = 'salestax'
3
+ spec.version = '0.0.2'
4
+ spec.authors = ['Matias Anaya']
5
+ spec.email = ['matiasanaya@gmail.com']
6
+ spec.summary = %q{A basic sales tax calculator}
7
+ spec.description = %q{A basic sales tax calculator utility, that accepts a list of items and prints a receipt with taxes.}
8
+ spec.homepage = 'https://github.com/matiasanaya/sales-tax'
9
+ spec.license = 'UNLICENSE'
10
+
11
+ spec.files = `git ls-files -z`.split("\x0")
12
+ spec.executables = ['salestax']
13
+ spec.test_files = spec.files.grep(%r{^(spec)/})
14
+ spec.require_paths = ['lib']
15
+
16
+ spec.required_ruby_version = '~> 2.1'
17
+
18
+ spec.add_development_dependency 'rspec', '~> 3.1'
19
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: salestax
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matias Anaya
@@ -34,6 +34,8 @@ extensions: []
34
34
  extra_rdoc_files: []
35
35
  files:
36
36
  - ".gitignore"
37
+ - CONTRIBUTING.md
38
+ - LICENSE
37
39
  - README.md
38
40
  - bin/salestax
39
41
  - data/example_input_a.txt
@@ -48,6 +50,7 @@ files:
48
50
  - lib/sales_tax/line_item/medicine.rb
49
51
  - lib/sales_tax/line_item/parser.rb
50
52
  - lib/sales_tax/receipt.rb
53
+ - salestax.gemspec
51
54
  - spec/accountable_spec.rb
52
55
  - spec/features/sales_tax_spec.rb
53
56
  - spec/line_item_base_spec.rb