Novell Home

HowTo: Create a Home Cooks Recipe Book using the Linux Shell and RunRev GUI Part 4

Novell Cool Solutions: Feature
By Stomfi

Digg This - Slashdot This

Posted: 28 Jun 2005
 

StomfiLearning to use Linux at Home and Work
Welcome to my ongoing series of HowTo articles designed to help Linux newbies get comfortable with Linux. Before trying any of these HowTos, take a few minutes to study the prerequisites so you can hit the ground running.
--Stomfi

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

The good folks at Runtime Revolution have extended a special offer to Cool Solutions readers to make it easier for you to implement the great ideas in Stomfi's articles. They are offering it for free to Novell customers who know the secret code. See this page for details.

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

© 2014 Novell