Initial revision
0 parent commit 679888d44a92cd3eb544e09667a9f0d26c334228
@tundra tundra authored on 14 Jul 2001
Showing 5 changed files
View
221
0-StartHere.txt 0 → 100644
$Id: 0-StartHere.txt,v 1.1 2001/07/14 06:17:00 tundra Exp $
 
INTRODUCTION TO HB
==================
 
(BIG HINT: You do not care about HB as a useful program (probably),
but if you're trying to learn Python, it may help.)
 
 
What Is HB?
===========
 
HB is a fairly simple, but not trivial, Home Budget management program
written in the Python programming language. (You'll need a version of
Python for your operating system to run hb.py, the actual HB program.
You can get this at www.python.org.) HB is not going to replace
Quicken or MS-Money any time soon, but as the next section explains, I
had a good reason or two for writing it.
 
 
Why Did I Write It?
===================
 
Two Reasons:
 
Years ago - decades, actually - one of the very first programs I wrote
in BASIC was the inevitable Home Budget program. In those days it was
written in MBASIC on a TRS-80 Model 1 talking to cassette tape for
storage. Over the years the program evolved first to floppy disk,
then to MS-DOS, then to compiled QuickBASIC. It was never a very well
written program in the first place and all that evolving didn't make
it any prettier. It was a pain to maintain, but I actually *liked*
using it. You see, I hate all the complexity that GUIs brought to
personal computing. My finances are not like John D. Rockerfeller's
and I don't need a whiz-bang GUI, scratches-your-back-while-it-does-
your-taxes program just to figure out how much money I'm going to owe
at the end of the month, fer cryin' out loud. So, one reason I did HB
was to replace my old piece-of-crap BASIC program.
 
The (much more important) other reason I wrote HB was because I wanted
to learn the Python programming language. This is the reason you care
about HB at all, if you do (because only 1 out of 280 million
Americans (me) is actually going to use it for its intended purpose).
 
I'm a Computer Scientist by training and a lot of graduate school
(1 - M.S. and 1/2 Ph.D. ;) stuff I did involved languages and compiler
design and I just *love* fiddling around with new computer languages.
Over the years I've programmed some or a lot in: BASIC, Assembly
Languages, C, C++, LISP, FORTRAN, PL/M, PL/1, AWK, Perl, Visual BASIC,
Java, and Forth. When I first started playing with Python, I realized
that this was one of the very coolest languages I'd ever seen. It is
good for just about everything (except, probably, writing operating
systems), promotes good programming style, causes you to write lots of
good code *fast*, and, well, it's just plain FUN.
 
The problem is that, while you can learn the basic syntactic elements
and get a sense of semantics of a language from a tutorial, you really
don't "get it" until you write a non-trivial program. In my
experience, this is the only way to being learning the "idioms" of a
language. OTOH, if the program you start with is trying to solve too
complex a problem, you'll end up spending all your time on the problem
not the language. For instance, you probably do not want to make your
first Python program do non-linear differential equations (unless you
happen to be a whiz at them, in which case, you have my condolences ;).
 
So, I decided to reimplement my old home budget program in Python.
The program is solving an intellectually simple problem, but it is
non-trivial in the sense that it requires I/O (disk, printer,
keyboard), needs to manage a display space, needs to parse user input,
and so on. Along the way, I added some "nifty" features like unlimited
Undo capability and a table-driven command interpreter that's kinda cool,
if I do say so myself.
 
 
Why Am I Distributing It?
=========================
 
 
As I was writing (and rewriting, and rewriting, and ...) HB, it
occured to me that other people might be able to learn Python a little
faster if they had a complete application to look at. So, I went back
and heavily commented the code (about 50% of hb.py is *comments*),
wrote these docs and decided to make it available to anyone who wanted
to paw through it.
 
It also occurs to me that, having misspent my youth in academics,
people teaching Python programming might want to use HB for class
work. This would make me very happy. Too much of what is taught
involves "toy problems" that never get much beyond the trivial example
stage.
 
IMPORTANT NOTE: This is not intended to "teach" you Python or
programming or anything else. The idea is that you already know how
to program and have a basic understanding of how Python does things.
The point here is to "Put It All Together" and show an application of
all the pieces as an integrated whole.
 
 
Program Features
================
 
This is a pretty straighforward Budget Management system. Nothing too
esoteric here, really:
 
Unlimited Different Budgets
Up to the limit of your disk space.
 
Unlimited Accounts
Well, the limit is what you can squeeze on a single screen.
 
Unlimited UnDo
Limited by memory - if you run out of UnDo stack,
you're using HB for WAY too complicated budgets ;)
 
Prints Reports To Printer (Win32 Only) Or Disk File (All OS)
 
Handles Monthly "Burn Rate" For Each Account
 
Pay, Debit, Credit, And Transfer Funds
 
Balance The Budget
 
Read And Write Budget Files
 
 
Code Features
=============
 
This is probably what you really care about:
 
Uses Every Major Python Data Structure
Tuples, Tuples Of Tuples, Tuples Of Tuples Of Tuples, Lists,
Dictionaries, and Objects
 
Has Mixture Of OO And Procedural Code
Python is *really* good at this.
 
Uses A Regular Expression For Input Validation
 
Uses 'eval' To Demonstrate Runtime String Execution
 
(Some) Exception Handling
 
Disk, Keyboard, And Device I/O
 
Code Is Completely "UnStrung"
There are no literals (other than newlines) embedded in the code
which makes maintenance and internationalization very simple.
 
Completely Table-Driven Command Interpreter
Kinda cool, actually. A little complicated at first, but it makes
adding new features a snap AND prepares the way for mating the program
to a GUI if you must have one in your life.
 
Stack-Based Context Management And UnDo Facilities
 
LOADED With Explanatory Comments So You Understand What's Going On
 
 
Caveats And Acknowledgements
============================
 
I am an experienced programmer, but not an experienced *Python*
programmer. This means that not everything in HB is necessarily good
Python - I hope it is, it probably mostly is, but it probably also has
some warts. If you *are* an experienced "Pythonista" and you see
something in HB that makes you just wince, send me an email and let me
know what needs fixin'.
 
I also want to take a moment and thank the many friendly people of
comp.lang.python who answer my many stupid questions promptly and
correctly both on the newsgroup and privately.
 
Also, a big "Thank You" to www.diveintopython.org for one of the best
language tutorials I've ever seen. It is a work-in-progress, but a
"must" if you are learning Python.
 
Finally, a huge thanks to GVR and the many people who designed,
implemented, and refine one of the slickest programming languages
around...
 
 
How To Proceed
==============
 
All the files in HB documentation directory begin with a number. All
you have to do is read them in order. This document begins with '0-',
so the next thing you need to read is the one starting with '1-' (the
HB Licensing document - you MUST read and agree to those terms to make
use of HB in any way)... and so on.
 
 
Political Statement
===================
 
Some people view Open Source and Freeware as Marxists:
 
"Code should be made available because it belongs to everyone
and there is a moral imperative to make it freely available."
 
In other words, "From each according to their ability, to each according
to their need." (This really worked well in the 20th Century, don't you
think?)
 
This is drivel of the worst kind. If you don't understand why, read
"Economics In One Lesson" by Hazlitt, "The Road To Serfdom" by Hayek, and
"Capitalism, The Unknown Ideal" by Rand. I view HB as a Libertarian:
 
"HB is my property (well, the property of my corporation). I
created it on my own, so it belongs to me. I make it freely
available because *I can* and *I want to*. The *only* way I
make it available is if you agree to my licensing terms. If
you do not, don't use HB. I have no moral obligation to
make it available at all, I do it because it suits me."
 
 
Questions Or Comments?
=====================
 
Send 'em to: tundra@tundraware.com
View
85
1-HB-License.txt 0 → 100644
$Id: 1-HB-License.txt,v 1.1 2001/07/14 06:17:00 tundra Exp $
 
In order to use,study, modify, or copy HB, you must read and agree to
all the licensing terms below. If you do not agree with or understand
*ANYTHING* you see in this document, you are NOT granted a license to
use, study, modify, or copy HB. By using, studying, modifying, or
copying HB, you are agreeing to all the terms of the HB LICENSE below
in their entirety.
 
 
HB LICENSE AGREEMENT
====================
 
1) DEFINITIONS
 
Throughout this Agreement the term "HB" is used to mean:
 
The program, hb.py. All its documentation and support files.
Anything included in the HB software distribution from TundraWare Inc.
 
Throughout this Agreement the term "User" is used to mean:
 
Any person who enagaged in any of the following activities:
 
Uses the HB program in any way.
 
Reads the HB documentation.
Studies the HB program source code or supporting files.
 
Makes use of any part of the HB software distribution for any
purpose.
 
Duplicates and/or distributes the HB software distribution.
 
2) OWNERSHIP
 
HB Is Copyright (c) 2001, TundraWare Inc., All Rights Reserved.
 
 
3) TERMS
 
Permission is hereby granted to the User for the duplication and use
of HB so long as ALL the following conditions are met:
 
1) The User of HB understands and agrees that this is EXPERIMENTAL
SOFTWARE which is provided "AS-IS" with no warranties expressed
or implied by TundraWare Inc.
 
2) The User acknowledges HB has NOT been tested for:
 
a) Correct operation
b) Freedom from unintended consequences
c) Any operation or condition which might cause damage to the
User's or other machines, software, networks, or data, or
which might cause any breach of system security of the
User's system(s) or any other systems.
 
3) By using HB in any way, the User does so at their own risk and
agrees to hold TundraWare Inc. harmless for any damage,
direct or indirect, that this software may or does cause to
the User's computational environment, including, but not
limited to, the User's or others' hardware, software, network,
or data. THE USER FURTHER AGREES TO HOLD TUNDRAWARE
INC. HARMLESS FOR ANY ECONOMIC DAMAGE OR ANY OTHER ADVERSE
CONSEQUENCE, DIRECT OR INDIRECT, CAUSED BY THE USE OF HB.
 
4) If duplicated and/or distributed, no fee beyond reasonable
duplication charges may be charged for HB. No commercial
use of HB which involves any remuneration beyond these
duplication charges is permitted.
 
5) Any distributed copies of HB must include all the originally
provided software, documentation, and licensing information in
their original distribution format and packaging without any
modifications.
 
 
 
 
IF YOU DO NOT UNDERSTAND, OR CANNOT ABIDE BY ANY OF THESE CONDITIONS,
DO NOT USE HB.
 
To report bugs or suggest improvements, contact: tundra@tundraware.com
View
148
2-HowToUse.txt 0 → 100644
$Id: 2-HowToUse.txt,v 1.1 2001/07/14 06:17:00 tundra Exp $
 
HOW TO USE HB
=============
 
Before you dive into the code, it's useful to get a basic understanding
of how HB works from a user's point-of-view.
 
Budget Files
============
 
Before starting HB, you have to create a budget file with any old
editor (one of the ways you might extend HB as a programming exercise
would be adding the ability to create new budgets). This file
is organized like this:
 
Cash 3000 2343.44
Books 100 55.41
Creditcards 0.0 562.8
Dsl 39.0 0.0
Rent 1000.0 0.0
Savings 200.0 4320
Utilities 160.0 0.0
 
 
The first column is the the account Name.
 
The second column is the amount you add to that account (the "Burn Rate")
every month. For example, the Cash account has a burn rate of 3000 -
that is presumably how much money you bring in to that budget every
month.
 
The third column is the current account Balance. You can either set
this to be 0 intitially, and enter the amounts through HB, or prime
the Budget with the amount you want.
 
The columns should be separated with spaces and terminate with a
single new line. HB does very little checking on the sanity of the
budget file, so mis-formatting this file will blow up the program.
Waddya want fer free?
 
You then have to make one edit to the hb.py file and tell it where
it can find its budget files. At the top of the program a very few
variables that users should change. You'll see something like this:
 
BASEDIR = "./"
BUDFIL = "hb.txt"
 
This says that the directory which holds HB budget files is the
current directory, and the default budget files is called 'hb.txt'
which is what HB will load when it starts up.
 
 
Running The Program
===================
 
Once it starts up, HB will show you a screen something like this:
 
------
HomeBudget - Version 1.71 - Fri Jul 13 23:10:33 2001
Copyright (c) 2001, TundraWare Inc. All Rights Reserved.
Budget: hb.txt
 
 
 
 
Books 55.41 Creditcards 562.8 Dsl 0.0
Rent 0.0 Savings 4320.0 Utilities 0.0
-------------------------------------------------------------------------------
Total Cash: 2343.44 Total Debits: 4938.21 Available Cash: -2594.77
 
 
 
<!>abandon <B>alance <C>redit <D>ebit <L>oad <M>onthly
<P>ay save+<Q>uit p<R>int <S>ave <T>ransfer <U>ndo
 
Command?
-----
 
The 'hotkey' to invoke a command is delimited by <>.
 
If you key in an EOF at any point, the program will exit w/o saving
 
If you key in a Ctrl-C, it does nothing under Unix, and aborts the
program under Win32.
 
If you hit Enter to any prompt (blank line) the program will abort
the transaction underway and take you back to this main screen prompt.
 
The columns above the separator line are your accounts. The sum of
all these accounts is your Total Debits.
 
Available Cash = Cash-Debits In this case you're running a big deficit,
so you better either get more Cash, or quit reserving so much in Savings ;)
 
 
Here's roughly what the commands do:
 
<!>abandon Exit w/o saving and recalculate budget.
 
<B>alance If you have more Cash than Debits, put the excess in Savings.
If you have more Debits than Cash, move money from Savings into
Cash. This feature requires there be a Savings account. If
there is not one, the feature will not appear on the toolbar.
 
<C>redit Add funds to an account.
 
<D>ebit Remove funds from an account.
 
<L>oad Load a new budget. If you just hit Enter at the prompt,
it will reload the budget you are working on.
 
<M>onthly Increase each account by its monthly Burn Rate amount.
You typically do this at the beginning of a month in anticipation
of getting paid and having your recurring bills show up.
 
<P>ay Each account reflects an indebtedness or liability.
When you pay from an account, the account balance is
reduced by the amount you select and Cash is decremented
accordingly (Unless your try to pay from the Cash account -
then it only subtracts the amount once.) When prompted
for an amount to pay, if you just hit Enter, HB understands
this to mean you want to pay the entire amount in that account
and does so.
 
save+<Q>uit Ummm, it saves the current budget and quits the program.
 
p<R>int Print a report to the last used reporting file/device.
Initially, by default, under Win32, this is LPT1: Under
any other OS, it is a file called 'HB-Report.txt'. But,
when you select this option it will prompt you for the
name of an output file or device. If you just hit Enter,
it takes the default, otherwise you can enter whatever
you like.
 
<S>ave Save the current budget, but don't exit.
 
<T>ransfer Move funds from one account to another.
 
<U>ndo UnDo the last thing you did. You can keep UnDoing things
until HB is back to the point at which it started. UnDo
has no effect for Saves or Prints, but it does return to
the state prior to loading a budget. Try it - it's fun.
 
 
 
 
View
222
3-UnderTheHood.txt 0 → 100644
$Id: 3-UnderTheHood.txt,v 1.1 2001/07/14 06:17:00 tundra Exp $
 
UNDER THE HOOD: COMMENTARY ON HOW HB WORKS
==========================================
 
The hb.py source file is full of (hopefully) helpful comments. You'll
probably get the most help from reading that source. Just so you can
kind of see the overall picture, this document is intended to give you
more of a "Thousand Foot View" of the code. At the end, I talk a bit
about some of the design decisions I made, and why.
 
 
Code Structure
--------------
 
The code is laid out like this:
 
Variables Available For User Modification
Imports
 
Aliases & Redefinitions
 
Constants & Literal Strings
 
Prompts & Strings Used In The Application
The Command Table
This is where all available commands are described in
tabular fashion. This is the heart of how the Command
Interpreter knows what to do when the user presses a key.
 
Global Variables & Data Structures
 
Class Definitions
 
Main Handler Function Definitions
There is one of these for each feature the user can
select. So there is a Main Handler for Print, Transfer,
Save, and so on.
 
Support Function Definitions
Functions we need to do the "housekeeping" inside HB.
 
Startup Code
(This is where the program actually begins execution.)
This initializes some variables and objects, loads the
default budget and generally gets things ready to go.
 
Command Interpreter Loop
Sits and waits for the user to key in a command and
then does something about it.
 
 
Comments On The Command Interpreter
-----------------------------------
 
The Command Interpreter is the most complicated part of hb.py, but
it's pretty well commented in the code. Here's the Big Picture:
 
 
Setting Up The Command Interpreter
----------------------------------
 
The Options table at the top of the program has a description
for each feature (like Save or Transfer) that the user can invoke.
This "description" consists of two parts:
 
1) The feature *name*, and the *name* of the Main Handling Function
for that feature.
 
2) A list (a tuple, really) of all the functions that need to
be run to gather arguments from the user in order for
the feature's Main Handling Function to actually be run.
For example, before we can run Transfer(), we have to discover
the "From Account", the "To Account", and "How Much".
 
When you want to add a new feature you have to:
 
1) Write a Main Handler Function for it
2) Write any needed Support Functions
3) Put an entry in Options for it.
 
These descriptions are all ***STRINGS***. Every time HB loads a new
budget, it reads these strings to dynamically construct a so-called
"Jump Table" (sometimes these are called "Dispatch Tables"). This
Jump Table is what the Command Interpreter uses to figure out what to
do when the user asks for something.
 
You would think that you only need to build the Jump Table once and be
done with it, but that won't work. Some features may only be available
if certain accounts are present in a given budget. For example,
Balancing an account depends on the presence of another account
named by the BALACCT variable (in the user variable section at the
top of the code). If this account is not present in a new budget,
then HB is smart enough to suppress showing that option on the toolbar.
That's why you have to build the JumpTable every time you Load a budget.
 
 
Running The Command Interpreter
-------------------------------
 
Here, roughly, is what the Command Intepreter is doing in pseudo-code:
 
If we're not done:
 
Display the state of the budget to the user.
 
Wait for the user to keyin a legitimate hotkey requesting a feature.
A blank line just means to skip it and go back to the top of this
whole Command Interpreter loop.
 
Use the hotkey to index into the Jump Table (a Python dictionary)
and retrieve the dispatch information for that feature.
 
From this dispatch information we get:
 
The name of the Main Handler Function that is responsible
for that feature.
 
The names of all the Support Functions (and their user
prompts, if any) that we have to run to build a list of
arguments to send to the Main Handler Function.
 
Run each of the Support Functions we found and add their return
values to args list.
 
Create an Invocation Record - a list consisting of two things,
a reference to the Main Handler Function, and the args we're sending
it - and push it onto the UnDo stack. We skip this step if the
requested feature was UnDo - you cannot UnDo and Undo in HB.
 
Now, call the Main Handler Function and pass it the args list we
just built.
 
 
Why The Two Step Process From Options-> JumpTable ->Command Interpreter?
Why Not Just Code The Jump Table Directly?
------------------------------------------------------------------------
 
Actually that's how I started out. I directly encoded a Jump Table
something like this:
 
JumpTable = {
"B" : ("<B>alance", Balance, ((BalAmount, ""),))
... And so on
}
 
 
There were several reasons I didn't like this approach:
 
1) Minor Issue: With a static Jump Table, I'd have to add a flag to
determine whether the feature was enabled or not (To supress things
like Balance when BALACCT is not present in the budget). Not a big
deal and, in retrospect, probably a little cleaner than the way
I ended up doing it.
 
2) Major Issue: Notice that in this version of reality Balance and
BalAmount are *object references* NOT strings. (A function in
Python is an object - *everything* in Python is an object. So a
function reference is really an object reference.) This means that
the JumpTable would have to appear in the code *after* these
functions were defined. (Python hates forward references - there's
probably some way around this, but I've not bothered to figure it
out.) This would have meant having literal strings in two places
in the code - at the top, where most of the literals are defined
and right after all the function definitions. I take this
Destringing and Maintenance stuff pretty seriously - it is the way
things are gonna be from here forward. Putting stuff that needs to
be changed in different places in the code it just begging for
trouble later on.
 
3) I wanted to play around with the 'eval' feature of Python.
It works very nicely, BTW ;))
 
 
Why Bother With All This Hocus-Pocus?
-------------------------------------
 
Yeah! Why not just have a bunch of nested if-elif pairs to do the same thing.
Several reasons, Grasshopper:
 
1) This way keeps all the literal strings out of the code. Destringing is
pretty important as the globe gets smaller and smaller. Not everyone
speaks and reads English (which is one of the reasons Python has Unicode
character support). If you code is littered with strings, translating it
to another language is a real pain and likely to be buggy. This way, every
string you may ever have to change sits happily at the top of the code
before even the first class definition. Repeat after me:
 
SEPARATE & ISOLATE LITERALS FROM LOGIC
SEPARATE & ISOLATE PRESENTATION FROM COMPUTATION
SEPARATE & ISOLATE DATABASE FROM DATA STORAGE INFRASTRUCTURE
SEPARATE & ISOLATE APPLICATIONS FROM SYSTEMS INFRASTRUCTURE
SEPARATE & ISOLATE SYNCHRONOUS EXECUTION SEMANTICS
 
The failure to do these things is why the Internet is such a buggy mess,
why scale is so hard to achieve, why security is tough to do....
(Ooops, I just regressed back into Systems Architect mode. Sorry 'bout
that, I'm all better now.)
 
2) Changing things and general maintenance is much simpler. It's relatively
easy to maintain a table and some functions. It is VERY painful to maintain
nested conditionals as the program logic evolves.
 
3) Adding new features is a snap. Try it and see. Just add a trivial feature
that, say, displays how many accounts are in the current budget. Once you
understand what's going on here, it takes about 5 minutes.
 
4) More structure means less bugs, means less testing (hopefully).
 
5) This style of event-driven, table-lookup coding is well suited for
GUIs. Someday, I'm gonna break down and learn Tkinter and/or
wxPython. When I do, this code structure should change very little.
I'll have to go to new routines for keyboard input and display, but
the rest of it should pretty much stay the same way.
 
 
 
 
View
66
4-Limitations-Enhancements.txt 0 → 100644
$Id: 4-Limitations-Enhancements.txt,v 1.1 2001/07/14 06:17:28 tundra Exp $
 
KNOWN LIMITATIONS AND PROBLEMS OF HB
====================================
 
- For some reason, I cannot get the Win32 version of Python 2.1 to
properly trap a KeyboardInterrupt. The code is there and it works
on Unix (at least FreeBSD 4.3), but it just plain does not want to
cooperate under WinDoze.
 
- There are several places where I don't bother to check for error
conditions which can cause the Python runtime to burp and end with a
traceback.
 
- There is almost no checking for sanity in the budget file being
loaded, so it is easy to blow up HB with a bad budget.
 
 
 
ENHANCEMENTS TO HB YOU MIGHT TRY YOURSELF
=========================================
 
- Check correctness and/or be more forgiving about reading in budget
files.
 
- Use the getopt library to add command line processing features to
HB. You could, for instance, use the command line to name the
initial budget file or default print device/file.
 
- Add a new user feature of your own. This means adding the correct
entry in Options, writing a Main Function Handler for the new
feature and any needed support functions to compute arguments.
Remember, keep literal strings out of the code and in the tables at
the beginning of the program. A good place to start would be
features for creating new budgets, and adding or deleting accounts
within a budget.
 
- Modify the Print() Main Function Handler to properly print under
Unix or MacOS.
 
- Get the program running using curses on Unix or a similar screen
management system for Win32.
 
- GUIify HB with Tkinter or wxPython. The code is already setup for
it to be event driven (more or less) in a GUI.
 
- Gut the Jump Table and Command Interpreter logic out of HB and write
your own application using this approach.
 
- Rewrite all the arithmetic and currency objects in HB to use BCD or
the FixedPoint library to avoid all the rounding error garbage
floats give you.
 
- Modify the Command Interpreter to accept multiple arguments at once
to avoid the endless input dialog. For example:
 
t Sav Ph 300
 
Would mean to transfer 300 (Dollars/Pesos/Marks ...) from the Savings
to to Phone Accounts.
 
- Replace the (really lame) ABORTINPUT global flag mechanism with
a custom exception object to abort the current input dialog.
Python lets you define your own exception types, and this is a
good application of same.