""" Sum and print masses for a packing list. """ import math INDENTATION=3 # units are grams # masses >= 100g via food scale # masses < 100g via hand-held postal scale # 1 lb = 453.59237g (at 1G) GRAMS_PER_POUND = 453.59237 # 1 lb = 16 oz OUNCES_PER_POUND = 16 GRAMS_PER_KILOGRAM = 1000 MASSES = { 'Pack' : { 'REI Ridgeline 65': 1920, 'Pack Cover (UltraSil)' : 92, }, 'Water' : { '1L Bottle (Nalgene, empty)' : 180, '3L Reservoir' : { 'Platypus Hose' : 65, 'Platypus Reservoir' : 90, }, '1L Water (bottle)' : 1000, #'3L Water (reservoir)' : 3000, '2L Water (reservoir)' : 2000, 'SteriPen Classic' : { 'Body + cap' : 90, '4xAA (Li Energizer)' : 55, 'Pouch' : 35, } }, 'Food' : { 'Dehydrated Dinners' : { 'BP Beans & Chile for 2' : 210, 'BP Beans & Rice for 2' : 180, 'MH Beef Stew for 1' : 140, 'MH Pasta Primavera for 1' : 145, }, '6x Bagels' : 665, 'Peanut butter + container' : 215 + 23, '6x Granola bars' : 48*6, 'Turkey jerkey' : 135, 'Trail mix (8oz bag)' : 320, 'Trail mix (partial 16oz bag)' : 535, 'Pepper mangoes' : 180, 'Dehydrated Tomatoes' : (55 + 35 + 20 + 27 + 42), 'Salt' : { 'Film Cannister' : 5.5, 'Salt' : 29, }, '4xTeabags + baggie' : 14.5, 'Food stuff sack (20L StS)' : 130, '2x Plastic containers' : 26 + 35, }, 'Storage & Shelter' : { 'Sleeping pad' : { 'Thermarest NeoAir (regular)' : 415, 'Thermarest stuff sack' : 15, '2xPatches' : .2, }, 'Sleeping Bag' : { 'Stuff sack' : 64, 'Bag' : 1515, # storage sack: 120 # bag + stuff sack: 1575, bag = 1511 # bag + storage sack: 1640, bag = 1520 }, 'Tent (Sierra Designs LightYear)' : { 'Footprint' : { 'Footprint' : 160, 'Footprint sack' : 13, }, 'Main' : { 'Enclosure' : 510, 'Rain fly' : 465, 'Sack' : 38, }, 'Staking' : { 'Bag' : 8.5, 'Cords (for rain fly)' : 18, '7xStakes' : 73.5, # bag + stakes = 82 }, 'Poles' : { 'Short pole' : 76, 'Long pole' : 150, 'Bag' : 21, }, }, }, 'Clothing' : { # except things being worn 'Tevas (Terra Fi 3)' : 0, # 818, 'Pants (REI Sahara)' : 0, # 320, 'Belt (REI Sahara)' : 0, # 25, 'Shirt (REI)' : 0, # 335, 'Underwear' : { 'ExOfficio boxers' : 0, # 92, 'ExOfficio boxer briefs' : 74, 'ExOfficio briefs' : 61, }, 'Tee shirt (CG)' : 190, 'Head Net + bag' : 13, 'Socks' : { '3xSweatsocks' : 3*43, 'Smartwool (light tan)' : 90, 'Smartwool (tan and lines)' : 75, 'Smartwool (black and argyle)' : 75, 'Smartwool (blue and lines)' : 85, }, 'Earbags' : { 'Pouch' : 2.5, '2xEarbags' : 11.5, }, 'Kerchief (red/worn)' : 27, 'Kerchief (blue/new)' : 34, 'Hat (White Rock)' : 83, 'Raincoat (eVent)' : 370, 'Oversocks (homemade)' : 135, 'Bug spray (Maxi DEET 15ml, full)' : 24, }, 'Cooking' : { 'Fork (GSI Outdoors)' : 12, 'Spoon (GSI Outdoors)' : 15, 'Spoon (translucent)' : 9.5, 'Knife (translucent)' : 17, 'Penny stove' : 32, 'Windscreen' : 42, 'Lighter' : 23, #'Fuel cannister' : 92, 'Fuel bottle (16oz plastic)' : 16, 'Denatured alcohol (~16oz)' : 414, 'Collapsible cup (Sea to Summit)' : 71, 'Pot (MSR Titan Titanium)' : { 'Pot' : 90, 'Lid' : 35, }, 'Paper napkins' : 2.5, }, 'Health & Hygene; Tools' : { 'Ziploc bag' : 7, 'Safety pins (med & small)' : 3, '2X Paper clips' : 2, 'Comb' : 8, 'Toothbrush' : 15, 'Retainer' : { 'Box' : 22, 'Top' : 3.5, 'Bottom' : 2.5, }, '(Toe)Nail clippers' : 43, 'Duct tape mini-roll' : 13.5, 'Moleskin' : 12.5, 'Cough drop' : 5, 'Tiger Blalm (mostly full)' : 58, 'Badger Balm (mostly empty)' : 23, 'Sunscreen (half)' : 47, 'Skin ointment (mostly empty) and clip' : 5, '24xQ-tips + baggie' : 15, 'Pen (Micron 005)' : 8.5, 'Notebook (Moleskine 120p 7.5x10 soft)' : 135, 'Pocket knife (Swiss Army)' : 65, 'Flashlight' : { 'Body' : 120, '3xAAA (Alkaline)' : 34, }, 'Watch (Timex, half wrist strap)' : 24, 'Cell phone (Nokia)' : 95, 'Wallet' : { 'Might Wallet (Dynomighty Design)' : 12, '5xCards' : 22, 'Bank notes' : 15.5, 'Other papers' : 4, }, 'Metal whistle' : 4.5, "Cord (50'?)" : 47, }, 'Tech' : { 'Camera' : { '55-200mm + caps' : 370, '18-55mm + caps' : 230, '35mm f1.8 + caps' : 140, 'Nikon D40x Body, straps, cap' : 615, 'Lens pouch (Tamrac)' : 120, 'Remote' : { 'Remote' : 11, 'Pouch' : 1, }, 'Mini tripod' : 60, }, 'Audio' : { 'Sony PCM-M10 + wrist strap' : 135, '2xAA (Alkaline/Sony)' : 44, 'Windscreen + bag' : 8.5, 'Micro SD (16GB)' : .5, }, 'GPS' : { 'Garmin eTrex Legend HCx' : 130, 'Strap + snap' : 8, 'Micro SD (4GB)' : .5, '2XAA (NiMH Sanyo eneloop)' : 52, }, 'Solar Charger' : 95, 'Data Storage' : { 'Ziploc bag' : 5.5, 'USB cable (folded)' : 44, 'USB cable (coiled)' : 46, '500GB HD (Cavalry)' : 165, 'MicroSD USB adapter' : 2, 'MicroSD adapter & case' : 5, 'SD cards/cases (5 and 4)' : 25, }, 'Accessories' : { 'Ziploc bag' : 10, 'Earbuds (from iPod)' : 12.5, 'Headphone splitter' : 22, 'Camera blower' : 39, '2x Optical cloth' : 2*5.5, 'Lens cleaner' : 64, }, 'Power & Charging' : { 'Ziploc bag' : 7, 'Camera battery (second)' : 55, 'Cell phone charger' : 50, 'Camera battery charger' : 170, # camera charger: cord: 92 + brick: 78 '4xAA (NiMH, Sanyo eneloop, packaged)' : 98, '2xAA (NiMH, Sanyo eneloop, boxed)' : 67, }, }, } def printAlignedColumns(rows, padding=INDENTATION): if not rows: return columnWidths = [0,]*len(rows[0]) for row in rows: for i, s in enumerate(row): columnWidths[i] = max(columnWidths[i], len(s)) for row in rows: for w, s in zip(columnWidths, row): print s, print ' '*(w - len(s)), print def formatGramsAsImperial(grams): pounds = grams / GRAMS_PER_POUND partialPounds, unitPounds = math.modf(pounds) ounces = partialPounds * OUNCES_PER_POUND text = '' if unitPounds > 0: text += '%dlb' % unitPounds if text: text += ' ' text += '%.1foz' % ounces return text def formatGrams(grams): if grams > GRAMS_PER_KILOGRAM/2: return '%.1fKg' % (grams/GRAMS_PER_KILOGRAM) else: return '%dg' % grams def formatFraction(part, whole): f = part/whole text = '%2.2f' % f for level in (.04, .8, .2): if f >= level: text += '*' return text def getInfoLines(name, info, indent=0): lines = [] total = 0 for partName, mass in info.iteritems(): if isinstance(mass, dict): subLines, subTotal = getInfoLines( partName, mass, indent=indent+1) lines += subLines total += subTotal else: lines.append((indent+1, partName, mass)) total += mass lines.insert(0, (indent, name, total)) return lines, total lines, total = getInfoLines('Total', MASSES) formattedLines = [] indenter = ' '*INDENTATION for indentAmount, name, mass in lines: f = mass/total #if f < .01 and indentAmount > 1: # continue formattedLines.append(( indenter*indentAmount + name, formatGramsAsImperial(mass), formatGrams(mass), formatFraction(mass, total), )) printAlignedColumns(formattedLines)