HowTo: Create a Home Cooks Recipe Book using the Linux Shell and RunRev GUI Part 4
Novell Cool Solutions: Feature
By Stomfi
Reader Rating 
|
Digg This -
Slashdot This
Posted: 28 Jun 2005 |
This HowTo continues from Part One the creation of a home cook's recipe system using the shell and Runtime Revolution. Unlike MS Windows versions, it does not track nutritional information, as it is just for collecting favourite recipes, which you are going to eat because you like them. It does, however, attempt to give you a shopping list based on common package sizes, or where relevant a delicatessen quantity. It also has a space for a picture, which you can take to remind you what it should look like.
I think other useful bits of information for a home cook are what cooking pots, pans, bowls or dishes are needed, and if any special tools like graters, mixers, etc., are required, so you can plan your work accordingly. We can make this an added note so that useful information can be recorded. Besides the recipe, the number of servings and total preparation and cooking time can be shown.
Special Offer for Cool Solutions Readers: Free Copy of Runtime Revolution |
|
In this part we develop the shopping list system. A shopping list is different from an ingredients list, which deals with cups and teaspoons, because its quantities are either in package sizes or piece quantities.
In developing the shopping list system I found there were several needs to make calculations easier. One was to identify whether the ingredient was liquid or solid, another was to have the ingredient data in a different format and a third was to have a calculation quantities file to translate things like CUPS into grams.
Because of the liquid / solid requirement, rather than convert the existing ingredients file on the fly, it is more robust to save this data in the format required to make the shopping list system work while leaving the original system as is. Anyone who likes things a bit neater, may like to change the code using all the knowledge in these HowTos, to just use the new ingredients file for everything.
Just goes to show you that developing with shell tools and Runtime Revolution means you can change things without much difficulty, whereas if it had been done in a technical language, the changes would have been quite difficult.
Actually what we are doing here is quite mind boggling as we are taking a recipe ingredient name and quantity, like "1/2 CUP Chopped Parsley" and changing it to read "1 bunch Parsley" in the shopping list. This is all about matching patterns, and what you apply here, will help you to create other reports from arbitrarily worded information, formatted as knowledge. I remember using this type of shell scripting to find knowledge in the legal profession, a veritable mine field of multiple terms for the same thing.
This is a view of the recipe card for Tasmanian Prawns with American Pecan Pesto including picture.

You can see I've added a button for Shopping List to this card.
The script takes you to another card. The card can take you to further cards so we set off another of those flag names to make sure we only repopulate the card when we go there for the first time.
on mouseUp global FIRSTSTIME put "YES" into FIRSTSTIME go card "SHOP" end mouseUp
For the shopping list generator, we have to create another file which tells us what the package sizes will be for each ingredient. We will add fields to the Add Ingredients card to do this. Packages can be fixed or not depending if we can get arbitrary quantities from the delicatessen.
As soon as we enter an ingredient name, we run a shell script that fills in the package data, depending if it finds the name and if it is a package size.
The Add Ingredients card also gets modified for the liquid or solid tag. This tag gets used by a script which looks up another text file containing how many grams or mls there are in a cup for example. Of course if you live in the USA you are unfortunately using the old fashioned measurement system and will have to work things out for these. Metric is so easy, its a wonder you haven't changed your weights and measures yet since your money has been metric for ever. Australia went metric way back in the '60s.
This is the modified Add Ingredient card:
This is the modified card script:
on openCard put empty into field "NEWINGNAME" put empty into field "NEWINGQUANT" put empty into field "NEWINGMEAS" put empty into field "THISSMEAS" put empty into field "SQUANT" set the hilite of button "LIQUID" to false set the hilite of button "SOLID" to true global SOLID put "S" into SOLID end openCard
This is the modified ADD INGREDIENT NAME script.
on mouseUp ask "Enter Ingredient Name" put it into NEWINGNAME if NEWINGNAME <> empty then global NINGNAME put NEWINGNAME into NINGNAME put NINGNAME into field "NEWINGNAME" global NSHOPN global NSHOPQ global SOLID #Check to see if there is a shopping quantity for this ingredient put ($HOME & "/cookbook/bin/getspkg.sh" && quote & NINGNAME & quote) into GETSPKG replace return with empty in GETSPKG put the shell of GETSPKG into THISPACK put THISPACK into field "DEBUG" #Returns four lines one for name found, one for tag, one for quantity, one for measure if THISPACK <> empty then put line 1 of THISPACK into field "NEWINGNAME" put line 4 of THISPACK into field "THISSMEAS" put line 3 of THISPACK into field "SQUANT" put line 2 of THISPACK into STAG if STAG = "L" then set the hilite of button "LIQUID" to true put "L" into SOLID else put "S" into SOLID end if end if #Make sure NSHOPQ & NSHOPN is not empty if there is an old package if field "SQUANT" <> empty then put field "SQUANT" into NSHOPQ end if if field "THISSMEAS" <> empty then put field "THISSMEAS" into NSHOPN end if end if end mouseUp
Notice the use of "put line 1" etc. We haven't used this construction before, but it is just the thing for reading these lines into our card from the shell script.
This is the modified script for the Save button:
on mouseUp global NINGMEAS global NINGNAME global NINGQUANT global NSHOPQ global NSHOPN global UPDSHOP global SOLID if NINGMEAS <> empty and NINGNAME <> empty and NINGQUANT <> empty and NSHOPQ <> empty and NSHOPN <> empty and SOLID <> empty then if NINGMEAS = "NUMBER" then put "ONLY" into NINGMEAS end if put ( NINGQUANT & space & NINGMEAS & space & NINGNAME ) into NING replace return with empty in NING put ( NING & return ) after field "NEWINGREDS" of card "RNEW" #Put the NINGQUANT into decimals put ($HOME & "/cookbook/bin/decno.sh" && NINGQUANT) into DECNO replace return with empty in DECNO put the shell of DECNO into RINGQUANT #Build the file entry line #Get the next index number because it isn't created until the recipe is saved put ($HOME & "/cookbook/bin/idxno.sh") into GETIDX put the shell of GETIDX into RECIPEIDX put (RECIPEIDX & "#" & RINGQUANT & "#" & NINGMEAS & "#" & NINGNAME & "#" & SOLID) into NWSLINE replace return with empty in NWSLINE #Make sure each record is on a new line put (NWSLINE & return) into NSLINE #Don't save it in the real file until Ingedients card says so put ($HOME & "/cookbook/tmpsing.txt") into TMPSFILE open file TMPSFILE for append write NSLINE to file TMPSFILE close file TMPSFILE #Build the shopping quantity # if UPDSHOP = "YES" then #reset UPDSHOP put "NO" into UPDSHOP #Build pack file line put (NINGNAME & "#" & SOLID & "#" & NSHOPN & "#" & NSHOPQ) into NWRSHOP replace return with empty in NWRSHOP #Make sure each record is on a new line put (NWRSHOP & return) into NRSHOP #Don't save until ingredients card says so put ($HOME & "/cookbook/tmpspack.txt") into TMPPFILE open file TMPPFILE for append write NRSHOP to file TMPPFILE close file TMPPFILE end if end if go card "RNEW" end mouseUp
As you can see from this script, we do several new things. We create a decimal number for quantities like "1/2", we create an index number for saving the new ingredients format, we save the new ingredients line and the new shopping packages line into temporary files, which will only be written to the real files if we save the recipe.
This is the script for the package measurements:
on mouseDown put the selectedText of me into NEWSMEAS if NEWSMEAS <> empty then global NSHOPN global UPDSHOP put NEWSMEAS into NSHOPN put NEWSMEAS into field "THISSMEAS" put "YES" into UPDSHOP end if end mouseDown
This is the script for the package quantity button:
on mouseUp ask "Update shopping package quantity for this ingredient. eg 500 or 20 (for loose ingredients)" put it into SHOPQUANT if SHOPQUANT <> empty then global NSHOPQ global UPDSHOP put SHOPQUANT into NSHOPQ put NSHOPQ into field "SQUANT" put "YES" into UPDSHOP end if end mouseUp
This is one of the radio button scripts, the other one puts "L" into SOLID:
on mouseDown global SOLID put "S" into SOLID end mouseDown
This is the getspkg.sh shell script:
#!/bin/bash
#getspkg.sh ingredient
#
#return ingredient packages info if it exists
SHOPFILE="$HOME/cookbook/book/shoppkgs.txt"
#Touch SHOPFILE to make sure it exists
touch $SHOPFILE
#Find out whether exactly matching record exists
#If more than one exists, limit it to the first one
#The name can be changed in the input if this is not the one required
#
SHING="$1"
SHINGNUM='grep -i -c "$SHING" $SHOPFILE'
if [ $SHINGNUM -gt 0 ]
then
#Exists so check count
if [ $SHINGNUM -gt 1 ]
then
#Too many so cut off the first
SHINGMORE='grep -i "$SHING" $SHOPFILE'
SHINGEXIST='echo "$SHINGMORE" | head -1'
else
SHINGEXIST='grep -i "$SHING" $SHOPFILE'
fi
#It exists so return the real name, tag, quantity and measure in 4 lines
#Get the real name
REALNAME='echo "$SHINGEXIST" | awk -F"#" '{print $1}''
awk -F"#" -v SHINGD="$REALNAME" '{if($1 ~ SHINGD) \
{print $1 "\n" $2 "\n" $3 "\n" $4; exit}}' $SHOPFILE
fi
exit
Here is the decno.sh shell script:
#!/bin/bash
#decno.sh number
#The number could be a string which includes a fraction
#If it is it will be in two parts with the fraction as the last part.
#In any case there will be a?/ in there, so we look for that first.
FRACTION='echo "$1" | grep "/"'
if [ ${#FRACTION} -gt 0 ]
then
#There's a fraction in there
#Find out how many parts there are
#Here we use wc -w instead of piping it to awk as previously done
#So you can learn a better way of doing the same thing.
NUMP='echo "$1" | wc -w '
if [ $NUMP -gt 1 ]
then
#its in the second part.
FPART='echo "$1" | cut -d" " -f2'
NPART='echo "$1" | cut -d" " -f1'
else
#its all a fraction
FPART="$1"
NPART=0
fi
#Convert FPART to a decimal using bc
DPART='echo "scale = 2; $FPART"|bc -l'
if [ $NPART -gt 0 ]
then
#add this to the decimal using bc
THISNUM='echo "scale = 2; $NPART+$DPART"|bc -l'
else
THISNUM="$DPART"
fi
else
THISNUM="$1"
fi
echo $THISNUM
By this time you should be getting pretty familiar with awk and grep. You can see from these shell scripts that they are the flour and milk of information processing, at least in these shell recipes!
Using bc is great for doing maths with decimal points although the manual is a bit hard to follow, some othe examples can show you how to do accounting.
Here is the new script for the Save Recipe button on the Add Recipe cards:
on mouseUp
#These are the field values for the recipe.txt file
put field "TTRTIME" into TRTIME
put field "SERVES" into RSERVE
put field "NEWRTYPE" into NRTYPE
put field "NEWRNAME" into NRNAME
#This is the folder path for the txt files
global CBOOK
#This is the application folder
global COOK
#First check that the four values are filled in and there is something in ingredients and instructions
if TRTIME <> empty and RSERVE <> empty and NRTYPE <> empty and NRNAME <> empty
then
if field "NEWINGREDS" <> empty and field "NEWINSTRUCTS" <> empty
then
#Next we have to create an index number
put (COOK & "/bin/idxno.sh") into IDXNO
replace return with empty in IDXNO
put the shell of IDXNO into THISIDX
#Now we can write to the recipe.txt file
put (CBOOK & "/recipes.txt") into RFILE
replace return with empty in RFILE
# Create the file entry
put (THISIDX & "#" & NRTYPE & "#" & NRNAME & "#" & RSERVE & "#" & TRTIME) into RECIPE
replace return with empty in RECIPE
open file RFILE for append
write RECIPE to file RFILE
write return to file RFILE
close file RFILE
#Now we write the ingredients
put (CBOOK & "/ingredients.txt") into IFILE
replace return with empty in IFILE
#Create the file entry
put field "NEWINGREDS" into NEWING
open file IFILE for append
write THISIDX & return to file IFILE
write NEWING & return to file IFILE
write "#" & return to file IFILE
close file IFILE
#Now we write the instructions
put (CBOOK & "/instructions.txt") into SFILE
replace return with empty in SFILE
put field "NEWINSTRUCTS" into NEWINS
open file SFILE for append
write THISIDX & return to file SFILE
write NEWINS & return to file SFILE
write "#" & return to file SFILE
close file SFILE
#Now we write the notes if any
if field "RNOTES" <> empty
then
put (CBOOK & "/notes.txt") into NFILE
replace return with empty in NFILE
put field "RNOTES" into RNOT
open file NFILE for append
write THISIDX & return to file NFILE
write RNOT & return to file NFILE
write "#" & return to file NFILE
close file NFILE
end if
#Lastly we save the picture file reference if any
put the filename of image "PICT" into APIC
if APIC <> empty
then
put (CBOOK & "images.txt") into PICFILE
replace return with empty in PICFILE
put (THISIDX & "#" & APIC) into PICLINE
replace return with empty in PICLINE
open file PICFILE for append
write (PICLINE & return) to file PICFILE
close file PICFILE
end if
#Finally we transfer the shopping list files
#First the shopping ingredients
put("cat $HOME/cookbook/tmpsing.txt >> $HOME/cookbook/book/mingreds.txt") into SING
put the shell of SING into DUNSING
#Then the Package updates if any
put ($HOME & "/cookbook/bin/updspack.sh") into UPDSPACK
put the shell of UPDSPACK into DUNSPACK
end if
end if
go card "RTSELECT"
end mouseUp
This script includes the lines for transferring the new ingredients and packages data. The ingredients are just added to the end of the mingreds.txt file, but the packages are analysed and added with a new shell script.
Here is the updspack.sh shell script:
#!/bin/bash
#updspack.sh
#
#update shopping packages file from temp file
if [ -s $HOME/cookbook/tmpspack.txt ]
then
#Got some new packs to update
SHOPTEMP="$HOME/cookbook/tmpspack.txt"
SHOPFILE="$HOME/cookbook/book/shoppkgs.txt"
PACKTEMP="$HOME/cookbook/packtemp.txt"
#Touch FILES to make sure they exist
touch $SHOPFILE
touch $PACKTEMP
#For each line in SHOPTEMP find out whether record exists and is the same
TEMPLIST='cat $SHOPTEMP | wc -l'
#This uses wc -l to return the number of lines
for ((N = 1; N <= $TEMPLIST; N++ ))
do
L='awk -v NLINE=$N '{if(NR == NLINE) print $0}' $SHOPTEMP'
SPNAME='echo $L | cut -d"#" -f1'
SPQUANT='echo $L | cut -d"#" -f3'
SPMEAS='echo $L | cut -d"#" -f4'
SPNEXIST='grep -i "$SPNAME" $SHOPFILE'
if [ ${#SPNEXIST} -gt 0 ]
then
#Name exists so
#Check to see if it a quantity update
SPQEXIST='echo $SPNEXIST | grep "$SPQUANT"'
if [ $SPQEXIST -lt 1 ]
then
#It exists so do update action
awk -F"#" -v PNAME="$SPNAME" -v PQUANT="$SPQUANT" -v PMEAS="$SPMEAS"\
'BEGIN{IGNORECASE = 1;OFS = "#"};\
{if($1 ~ PNAME) {$3 = PQUANT; $4 = PMEAS}};\
{print}' $SHOPFILE > $PACKTEMP
if [ -s $PACKTEMP ]
then
#replace the original
cat $PACKTEMP > $SHOPFILE
fi
fi
else
#Add a new record
echo "$L" >> $SHOPFILE
fi
done
fi
exit
Now we should have the recipe data for the shopping list. We have to create the calculations file.
This is the file $HOME/cookbook/book/mclac.txt. The quantities haven't been measured. I just invented them in for testing. You will have to measure each one with scales. Notice that all the measurement quantities, like OUNCES and POUNDS etc are not in the file yet. Follow the GRAMS example.
CUPS#L#100#mls CUPS#S#100#grams TABLE SPOONS#L#12#mls TABLE SPOONS#S#12#grams CLOVES#S#1# BUNCHES#S#1# TEA SPOONS#S#3#grams TEA SPOONS#L#3#mls PINCHES#S#1#grams GRAMS#S#1#grams
This is the end of part 4. In part 5 we will develop the shopping list card and scripts, which will be the last for the shopping system.
Novell Cool Solutions (corporate web communities) are produced by WebWise Solutions. www.webwiseone.com
Learning to use Linux at Home and Work