Shopping cart
Fill a cart from a small catalog, change quantities, enter a discount code and check out with a receipt: the subtotal, the one discount taken (the largest that applies - discounts never stack), the tax and the total - from a text menu.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
A small shop: pick products from a catalog of six, change quantities, enter a discount code, look at the cart, and check out with a receipt. The receipt lists every line, then the subtotal, the discount, what is left after it, the tax and the total. Orders are numbered while the program runs.
main.eml- the menu and its questions, with their checkscatalog.eml- the products, the tax rate, the two offers that count by themselves and the two codes that count once enteredcart.eml- the cart's lines and sums: the subtotal, what each discount takes off, and which discount winsreceipt.eml- what the screen shows: the catalog with its offers, and the receipt with its notestext.eml- trimming, capitals, reading a whole number, and money
Discounts never stack. Every discount that applies to the cart - the offers (5% off from 50.00; notebooks 3 for the price of 2) and the code, if one has been entered - is worked out, and only the largest is taken; on a tie the code comes first. The receipt names the others as not added, with what each would have given, and says when an entered code is not used because the subtotal is below its minimum. One code counts per order: a new code replaces the old one.
All money is whole cents. A percentage off and the tax (8.25% of what is left after the discount) are rounded half up to the cent, in whole numbers.
What is checked: an item number is one in the catalog, and a quantity is a whole number from 1 to 99 (0 to 99 when changing one, where 0 removes the line); anything else asks again, and nothing cancels. One line holds at most 99. A code is one of the shop's codes, in any case; - removes it.
Sessions: sessions/basic.in shows the catalog, adds notebooks and coffee - the 3-for-2 offer wins - enters a code that beats it, adds a lamp and replaces the code with one that beats the 5% offer, changes a quantity, and checks out; sessions/bad-input.in uses the cart, a change and checkout while the cart is empty, types item numbers and quantities that are out of range, negative, words or empty, goes over 99 of one item, enters an empty, a missing and an unknown code, the same code twice, a code below its minimum, then a cart where the 5% offer beats the code, and removes everything.
Built on the verified corpus cases shopping-cart-total (lines of price times quantity summed into a total), receipt-formatter (a receipt with aligned columns, a subtotal, tax and a total) and the-two-discounts-were-added (two discounts do not add up to their sum).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 8
Pick a number from 1 to 7.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 4
The cart is empty.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 3
The cart is empty.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 6
The cart is empty.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 2
item number> 0
Type an item number from 1 to 6, or nothing to cancel.
item number> x
Type an item number from 1 to 6, or nothing to cancel.
item number> 7
Type an item number from 1 to 6, or nothing to cancel.
item number>
Cancelled.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 2
item number> 2
quantity> 0
Type a quantity from 1 to 99, or nothing to cancel.
quantity> 100
Type a quantity from 1 to 99, or nothing to cancel.
quantity> -1
Type a quantity from 1 to 99, or nothing to cancel.
quantity> two
Type a quantity from 1 to 99, or nothing to cancel.
quantity> 4
Added 4 x Pens, pack of 3.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 2
item number> 2
quantity> 96
At most 99 of one item; the cart has 4.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 2
item number> 2
quantity>
Cancelled.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 5
code (- removes it)>
No code entered.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 5
code (- removes it)> -
No code to remove.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 5
code (- removes it)> half
There is no code HALF.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 5
code (- removes it)> take5
Code TAKE5 entered: 5.00 off orders of 30.00 or more.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 5
code (- removes it)> TAKE5
Code TAKE5 is already entered.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 4
Your cart
Qty Item Price Total
4 Pens, pack of 3 2.25 9.00
--------------------------------------------
Subtotal 9.00
Tax 8.25% 0.74
Total 9.74
Code TAKE5 is not used: it needs a subtotal of 30.00.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 3
item number> 4
Green tea, 50 bags is not in the cart.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 3
item number> 2
new quantity (0 removes)> abc
Type a quantity from 0 to 99, or nothing to cancel.
new quantity (0 removes)>
Cancelled.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 2
item number> 5
quantity> 5
Added 5 x Desk lamp.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 4
Your cart
Qty Item Price Total
4 Pens, pack of 3 2.25 9.00
5 Desk lamp 24.00 120.00
--------------------------------------------
Subtotal 129.00
Discount (5% off from 50.00) -6.45
After discount 122.55
Tax 8.25% 10.11
Total 132.66
Not added: TAKE5, 5.00 off (5.00) - discounts do not stack.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 5
code (- removes it)> -
Code TAKE5 removed.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 3
item number> 2
new quantity (0 removes)> 0
Removed Pens, pack of 3.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 3
item number> 5
new quantity (0 removes)> 0
Removed Desk lamp.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 4
The cart is empty.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 7
Bye.
What was typed (53 lines)
8
4
3
6
2
0
x
7
2
2
0
100
-1
two
4
2
2
96
2
2
5
5
-
5
half
5
take5
5
TAKE5
4
3
4
3
2
abc
2
5
5
4
5
-
3
2
0
3
5
0
4
7
basic
interpreter: byte-equal
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 1
-- catalog --
1) Notebook 3.50
2) Pens, pack of 3 2.25
3) Coffee beans 1kg 12.99
4) Green tea, 50 bags 6.40
5) Desk lamp 24.00
6) USB cable 7.95
Offers - only the largest one that applies is taken:
5% off orders of 50.00 or more
Notebooks: 3 for the price of 2
code SAVE10: 10% off
code TAKE5: 5.00 off orders of 30.00 or more
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 2
item number> 1
quantity> 3
Added 3 x Notebook.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 2
item number> 3
quantity> 2
Added 2 x Coffee beans 1kg.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 4
Your cart
Qty Item Price Total
3 Notebook 3.50 10.50
2 Coffee beans 1kg 12.99 25.98
--------------------------------------------
Subtotal 36.48
Discount (notebooks 3 for 2) -3.50
After discount 32.98
Tax 8.25% 2.72
Total 35.70
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 5
code (- removes it)> take5
Code TAKE5 entered: 5.00 off orders of 30.00 or more.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 4
Your cart
Qty Item Price Total
3 Notebook 3.50 10.50
2 Coffee beans 1kg 12.99 25.98
--------------------------------------------
Subtotal 36.48
Discount (TAKE5, 5.00 off) -5.00
After discount 31.48
Tax 8.25% 2.60
Total 34.08
Not added: notebooks 3 for 2 (3.50) - discounts do not stack.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 2
item number> 5
quantity> 1
Added 1 x Desk lamp.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 5
code (- removes it)> SAVE10
Code SAVE10 replaces TAKE5 - one code per order.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 4
Your cart
Qty Item Price Total
3 Notebook 3.50 10.50
2 Coffee beans 1kg 12.99 25.98
1 Desk lamp 24.00 24.00
--------------------------------------------
Subtotal 60.48
Discount (SAVE10, 10% off) -6.05
After discount 54.43
Tax 8.25% 4.49
Total 58.92
Not added: 5% off from 50.00 (3.02) - discounts do not stack.
Not added: notebooks 3 for 2 (3.50) - discounts do not stack.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 3
item number> 1
new quantity (0 removes)> 2
Notebook: now 2.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 6
Order 1
Qty Item Price Total
2 Notebook 3.50 7.00
2 Coffee beans 1kg 12.99 25.98
1 Desk lamp 24.00 24.00
--------------------------------------------
Subtotal 56.98
Discount (SAVE10, 10% off) -5.70
After discount 51.28
Tax 8.25% 4.23
Total 55.51
Not added: 5% off from 50.00 (2.85) - discounts do not stack.
Paid - thank you. Order 1 is done.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 4
The cart is empty.
== Shop ==
1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit
choice> 7
Bye.
What was typed (23 lines)
1
2
1
3
2
3
2
4
5
take5
4
2
5
1
5
SAVE10
4
3
1
2
6
4
7
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P011 shopping cart: fill a cart from the catalog, change quantities, enter
# a discount code, and check out with a receipt. Only one discount is ever
# taken - the largest that applies; discounts are never added together.
import catalog
import cart
import receipt
import text
def ask_number(prompt, low, high, message):
# A whole number from low to high, asked again until one is typed; -1 if
# the answer is empty, which cancels.
while True:
text.trim(input(prompt)) => answer
if answer == "":
return 0 - 1
text.number(answer) => n
if n >= low and n <= high:
return n
message ^0
("Type an item number from 1 to " + str(len(catalog.products)) + ", or nothing to cancel.") => item_message
[] => lines
None => code
1 => order
True => running
while running:
"" ^0
"== Shop ==" ^0
"1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit" ^0
text.trim(input("choice> ")) => choice
if choice == "1":
receipt.show_catalog()
elif choice == "2":
ask_number("item number> ", 1, len(catalog.products), item_message) => n
if n == 0 - 1:
"Cancelled." ^0
else:
cart.product(n) => p
ask_number("quantity> ", 1, 99, "Type a quantity from 1 to 99, or nothing to cancel.") => q
cart.line_for(lines, n) => line
if q == 0 - 1:
"Cancelled." ^0
elif line == None:
lines + [[p, q]] => lines
("Added " + str(q) + " x " + p[1] + ".") ^0
elif line[1] + q > 99:
("At most 99 of one item; the cart has " + str(line[1]) + ".") ^0
else:
line[1] + q => line[1]
("Added " + str(q) + " x " + p[1] + " (now " + str(line[1]) + ").") ^0
elif choice == "3":
if len(lines) == 0:
"The cart is empty." ^0
else:
ask_number("item number> ", 1, len(catalog.products), item_message) => n
if n == 0 - 1:
"Cancelled." ^0
elif cart.line_for(lines, n) == None:
(cart.product(n)[1] + " is not in the cart.") ^0
else:
cart.line_for(lines, n) => line
ask_number("new quantity (0 removes)> ", 0, 99, "Type a quantity from 0 to 99, or nothing to cancel.") => q
if q == 0 - 1:
"Cancelled." ^0
elif q == 0:
cart.without(lines, n) => lines
("Removed " + line[0][1] + ".") ^0
else:
q => line[1]
(line[0][1] + ": now " + str(q) + ".") ^0
elif choice == "4":
if len(lines) == 0:
"The cart is empty." ^0
else:
"" ^0
receipt.show("Your cart", lines, code)
elif choice == "5":
text.upper(text.trim(input("code (- removes it)> "))) => typed
if typed == "":
"No code entered." ^0
elif typed == "-":
if code == None:
"No code to remove." ^0
else:
("Code " + code[0] + " removed.") ^0
None => code
elif catalog.find_code(typed) == None:
("There is no code " + typed + ".") ^0
elif code != None and code[0] == typed:
("Code " + typed + " is already entered.") ^0
elif code != None:
("Code " + typed + " replaces " + code[0] + " - one code per order.") ^0
catalog.find_code(typed) => code
else:
catalog.find_code(typed) => code
("Code " + typed + " entered: " + code[2] + ".") ^0
elif choice == "6":
if len(lines) == 0:
"The cart is empty." ^0
else:
"" ^0
receipt.show("Order " + str(order), lines, code)
("Paid - thank you. Order " + str(order) + " is done.") ^0
[] => lines
None => code
order + 1 => order
elif choice == "7":
False => running
else:
"Pick a number from 1 to 7." ^0
"Bye." ^0
Python projection (main.py)
import catalog
import cart
import receipt
import text
def ask_number(prompt, low, high, message):
while True:
answer = text.trim(input(prompt))
if answer == "":
return 0 - 1
n = text.number(answer)
if n >= low and n <= high:
return n
print(message)
item_message = "Type an item number from 1 to " + str(len(catalog.products)) + ", or nothing to cancel."
lines = []
code = None
order = 1
running = True
while running:
print("")
print("== Shop ==")
print("1) catalog 2) add 3) change quantity 4) cart 5) code 6) checkout 7) quit")
choice = text.trim(input("choice> "))
if choice == "1":
receipt.show_catalog()
elif choice == "2":
n = ask_number("item number> ", 1, len(catalog.products), item_message)
if n == 0 - 1:
print("Cancelled.")
else:
p = cart.product(n)
q = ask_number("quantity> ", 1, 99, "Type a quantity from 1 to 99, or nothing to cancel.")
line = cart.line_for(lines, n)
if q == 0 - 1:
print("Cancelled.")
elif line == None:
lines = lines + [[p, q]]
print("Added " + str(q) + " x " + p[1] + ".")
elif line[1] + q > 99:
print("At most 99 of one item; the cart has " + str(line[1]) + ".")
else:
line[1] = line[1] + q
print("Added " + str(q) + " x " + p[1] + " (now " + str(line[1]) + ").")
elif choice == "3":
if len(lines) == 0:
print("The cart is empty.")
else:
n = ask_number("item number> ", 1, len(catalog.products), item_message)
if n == 0 - 1:
print("Cancelled.")
elif cart.line_for(lines, n) == None:
print(cart.product(n)[1] + " is not in the cart.")
else:
line = cart.line_for(lines, n)
q = ask_number("new quantity (0 removes)> ", 0, 99, "Type a quantity from 0 to 99, or nothing to cancel.")
if q == 0 - 1:
print("Cancelled.")
elif q == 0:
lines = cart.without(lines, n)
print("Removed " + line[0][1] + ".")
else:
line[1] = q
print(line[0][1] + ": now " + str(q) + ".")
elif choice == "4":
if len(lines) == 0:
print("The cart is empty.")
else:
print("")
receipt.show("Your cart", lines, code)
elif choice == "5":
typed = text.upper(text.trim(input("code (- removes it)> ")))
if typed == "":
print("No code entered.")
elif typed == "-":
if code == None:
print("No code to remove.")
else:
print("Code " + code[0] + " removed.")
code = None
elif catalog.find_code(typed) == None:
print("There is no code " + typed + ".")
elif code != None and code[0] == typed:
print("Code " + typed + " is already entered.")
elif code != None:
print("Code " + typed + " replaces " + code[0] + " - one code per order.")
code = catalog.find_code(typed)
else:
code = catalog.find_code(typed)
print("Code " + typed + " entered: " + code[2] + ".")
elif choice == "6":
if len(lines) == 0:
print("The cart is empty.")
else:
print("")
receipt.show("Order " + str(order), lines, code)
print("Paid - thank you. Order " + str(order) + " is done.")
lines = []
code = None
order = order + 1
elif choice == "7":
running = False
else:
print("Pick a number from 1 to 7.")
print("Bye.")
catalog.eml
eml# P011 shopping cart - what the shop sells and the discounts it offers. A
# product is [number, name, price]; every price is whole cents.
[
[1, "Notebook", 350],
[2, "Pens, pack of 3", 225],
[3, "Coffee beans 1kg", 1299],
[4, "Green tea, 50 bags", 640],
[5, "Desk lamp", 2400],
[6, "USB cable", 795],
] => products
# Tax on what is left after the discount: 8.25%, in parts per ten thousand.
825 => tax_per_myriad
# A discount is [code, short name, description, kind, value, minimum, product].
# Kind "percent" takes value percent off the subtotal and kind "amount" takes
# value cents off it, either only from a subtotal of minimum cents; kind
# "3for2" makes every third one of product free. Offers count by themselves;
# a code counts only once it has been entered, and one code at a time.
[
["", "5% off from 50.00", "5% off orders of 50.00 or more", "percent", 5, 5000, 0],
["", "notebooks 3 for 2", "Notebooks: 3 for the price of 2", "3for2", 0, 0, 1],
] => offers
[
["SAVE10", "SAVE10, 10% off", "10% off", "percent", 10, 0, 0],
["TAKE5", "TAKE5, 5.00 off", "5.00 off orders of 30.00 or more", "amount", 500, 3000, 0],
] => codes
def find_code(typed):
# The code called typed (already in capitals), or None.
for d in codes:
if d[0] == typed:
return d
return None
Python projection (catalog.py)
products = [[1, "Notebook", 350], [2, "Pens, pack of 3", 225], [3, "Coffee beans 1kg", 1299], [4, "Green tea, 50 bags", 640], [5, "Desk lamp", 2400], [6, "USB cable", 795]]
tax_per_myriad = 825
offers = [["", "5% off from 50.00", "5% off orders of 50.00 or more", "percent", 5, 5000, 0], ["", "notebooks 3 for 2", "Notebooks: 3 for the price of 2", "3for2", 0, 0, 1]]
codes = [["SAVE10", "SAVE10, 10% off", "10% off", "percent", 10, 0, 0], ["TAKE5", "TAKE5, 5.00 off", "5.00 off orders of 30.00 or more", "amount", 500, 3000, 0]]
def find_code(typed):
for d in codes:
if d[0] == typed:
return d
return None
cart.eml
eml# P011 shopping cart - the cart and its sums. The cart is a list of lines
# [product, quantity] in the order they were added. Only one discount is ever
# taken: every discount that applies is worked out, and the largest wins.
import catalog
def product(n):
# The product numbered n, or None.
for p in catalog.products:
if p[0] == n:
return p
return None
def line_for(lines, n):
# The cart line for product n, or None.
for line in lines:
if line[0][0] == n:
return line
return None
def without(lines, n):
# The cart lines without the one for product n.
[] => kept
for line in lines:
if line[0][0] != n:
kept + [line] => kept
return kept
def subtotal(lines):
0 => s
for line in lines:
s + line[0][2] * line[1] => s
return s
def half_up(n, d):
# n / d rounded half up to a whole number, for n >= 0 and d > 0.
return int((2 * n + d - (2 * n + d) % (2 * d)) / (2 * d))
def amount_off(d, lines):
# What discount d takes off these lines, in cents; 0 if it does not apply.
if d[3] == "3for2":
line_for(lines, d[6]) => line
if line == None:
return 0
return int((line[1] - line[1] % 3) / 3) * line[0][2]
subtotal(lines) => s
if s < d[5]:
return 0
if d[3] == "percent":
return half_up(s * d[4], 100)
if d[4] > s:
return s
return d[4]
def choices(lines, code):
# Every discount that takes something off, as [discount, cents]: the code
# first if one has been entered, then the offers.
[] => found
if code != None:
amount_off(code, lines) => c
if c > 0:
found + [[code, c]] => found
for d in catalog.offers:
amount_off(d, lines) => c
if c > 0:
found + [[d, c]] => found
return found
def best(found):
# Where the largest choice is in found (the earlier one on a tie), or -1.
0 - 1 => b
for i in [0:len(found) - 1]:
if b == 0 - 1 or found[i][1] > found[b][1]:
i => b
return b
Python projection (cart.py)
import catalog
def product(n):
for p in catalog.products:
if p[0] == n:
return p
return None
def line_for(lines, n):
for line in lines:
if line[0][0] == n:
return line
return None
def without(lines, n):
kept = []
for line in lines:
if line[0][0] != n:
kept = kept + [line]
return kept
def subtotal(lines):
s = 0
for line in lines:
s = s + line[0][2] * line[1]
return s
def half_up(n, d):
return int((2 * n + d - (2 * n + d) % (2 * d)) / (2 * d))
def amount_off(d, lines):
if d[3] == "3for2":
line = line_for(lines, d[6])
if line == None:
return 0
return int((line[1] - line[1] % 3) / 3) * line[0][2]
s = subtotal(lines)
if s < d[5]:
return 0
if d[3] == "percent":
return half_up(s * d[4], 100)
if d[4] > s:
return s
return d[4]
def choices(lines, code):
found = []
if code != None:
c = amount_off(code, lines)
if c > 0:
found = found + [[code, c]]
for d in catalog.offers:
c = amount_off(d, lines)
if c > 0:
found = found + [[d, c]]
return found
def best(found):
b = 0 - 1
for i in range(0, len(found)):
if b == 0 - 1 or found[i][1] > found[b][1]:
b = i
return b
receipt.eml
eml# P011 shopping cart - what the screen shows: the catalog with its offers, and
# the receipt - the lines, the subtotal, the one discount taken, the tax and
# the total, then a note on every discount that was left out.
import catalog
import cart
import text
def show_catalog():
"" ^0
"-- catalog --" ^0
for p in catalog.products:
(" " + str(p[0]) + ") " + ("%-20s" % p[1]) + ("%8s" % text.money(p[2]))) ^0
"" ^0
"Offers - only the largest one that applies is taken:" ^0
for d in catalog.offers:
(" " + d[2]) ^0
for d in catalog.codes:
(" code " + d[0] + ": " + d[2]) ^0
def row(label, amount):
return ("%-34s" % label) + ("%10s" % amount)
def show(title, lines, code):
title ^0
(("%3s" % "Qty") + " " + ("%-20s" % "Item") + " " + ("%8s" % "Price") + " " + ("%9s" % "Total")) ^0
for line in lines:
(("%3d" % line[1]) + " " + ("%-20s" % line[0][1]) + " " + ("%8s" % text.money(line[0][2])) + " " + ("%9s" % text.money(line[0][2] * line[1]))) ^0
("-" * 44) ^0
cart.subtotal(lines) => s
row("Subtotal", text.money(s)) ^0
cart.choices(lines, code) => found
cart.best(found) => b
0 => off
if b >= 0:
found[b][1] => off
row("Discount (" + found[b][0][1] + ")", "-" + text.money(off)) ^0
row("After discount", text.money(s - off)) ^0
cart.half_up((s - off) * catalog.tax_per_myriad, 10000) => tax
row("Tax " + text.money(catalog.tax_per_myriad) + "%", text.money(tax)) ^0
row("Total", text.money(s - off + tax)) ^0
if code != None and cart.amount_off(code, lines) == 0:
("Code " + code[0] + " is not used: it needs a subtotal of " + text.money(code[5]) + ".") ^0
for i in [0:len(found) - 1]:
if i != b:
("Not added: " + found[i][0][1] + " (" + text.money(found[i][1]) + ") - discounts do not stack.") ^0
Python projection (receipt.py)
import catalog
import cart
import text
def show_catalog():
print("")
print("-- catalog --")
for p in catalog.products:
print(" " + str(p[0]) + ") " + "%-20s" % p[1] + "%8s" % text.money(p[2]))
print("")
print("Offers - only the largest one that applies is taken:")
for d in catalog.offers:
print(" " + d[2])
for d in catalog.codes:
print(" code " + d[0] + ": " + d[2])
def row(label, amount):
return "%-34s" % label + "%10s" % amount
def show(title, lines, code):
print(title)
print("%3s" % "Qty" + " " + "%-20s" % "Item" + " " + "%8s" % "Price" + " " + "%9s" % "Total")
for line in lines:
print("%3d" % line[1] + " " + "%-20s" % line[0][1] + " " + "%8s" % text.money(line[0][2]) + " " + "%9s" % text.money(line[0][2] * line[1]))
print("-" * 44)
s = cart.subtotal(lines)
print(row("Subtotal", text.money(s)))
found = cart.choices(lines, code)
b = cart.best(found)
off = 0
if b >= 0:
off = found[b][1]
print(row("Discount (" + found[b][0][1] + ")", "-" + text.money(off)))
print(row("After discount", text.money(s - off)))
tax = cart.half_up((s - off) * catalog.tax_per_myriad, 10000)
print(row("Tax " + text.money(catalog.tax_per_myriad) + "%", text.money(tax)))
print(row("Total", text.money(s - off + tax)))
if code != None and cart.amount_off(code, lines) == 0:
print("Code " + code[0] + " is not used: it needs a subtotal of " + text.money(code[5]) + ".")
for i in range(0, len(found)):
if i != b:
print("Not added: " + found[i][0][1] + " (" + text.money(found[i][1]) + ") - discounts do not stack.")
text.eml
eml# P011 shopping cart - reading what is typed and writing money. The
# interpreter that checks every session does not run string methods yet, so
# the text handling is written out here.
def trim(s):
# s without the spaces at either end.
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def upper(s):
# s with a-z turned into A-Z; everything else as it was.
"abcdefghijklmnopqrstuvwxyz" => lower_letters
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" => upper_letters
"" => out
for c in s:
c => d
for i in [0:25]:
if lower_letters[i] == c:
upper_letters[i] => d
out + d => out
return out
def number(s):
# The value of s if it is digits only (at least one), otherwise -1.
if s == "":
return 0 - 1
0 => n
for c in s:
if not (c in "0123456789"):
return 0 - 1
n * 10 + int(c) => n
return n
def money(c):
# Whole cents as 1,234.50, built from the digits of the integer.
str(c) => s
while len(s) < 3:
"0" + s => s
s[0:len(s) - 2] => whole
s[len(s) - 2:len(s)] => cents
"" => grouped
len(whole) => i
while i > 3:
"," + whole[i - 3:i] + grouped => grouped
i - 3 => i
return whole[0:i] + grouped + "." + cents
Python projection (text.py)
def trim(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def upper(s):
lower_letters = "abcdefghijklmnopqrstuvwxyz"
upper_letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
out = ""
for c in s:
d = c
for i in range(0, 26):
if lower_letters[i] == c:
d = upper_letters[i]
out = out + d
return out
def number(s):
if s == "":
return 0 - 1
n = 0
for c in s:
if not c in "0123456789":
return 0 - 1
n = n * 10 + int(c)
return n
def money(c):
s = str(c)
while len(s) < 3:
s = "0" + s
whole = s[0:len(s) - 2]
cents = s[len(s) - 2:len(s)]
grouped = ""
i = len(whole)
while i > 3:
grouped = "," + whole[i - 3:i] + grouped
i = i - 3
return whole[0:i] + grouped + "." + cents