HowTo: Create a Home Cooks Recipe Book using the Linux Shell and RunRev GUI Part 2
Novell Cool Solutions: Feature
By Stomfi
Reader Rating 
|
Digg This -
Slashdot This
Posted: 15 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 second part we develop the display and search parts of the system.
This is a view of the first screen with a recipe type to choose:
In the first part of this HowTo we used RunRev to start a new stack and named it COOKBOOK. The card was called RTSELECT, with the label "Cook Book". It was populated with a scrolling list field called RTYPES, the label as shown, and these two buttons. The image field is just to fill up the blank space. We created The ADD NEW RECIPE script, its card called RNEW and its sub cards, and tested "add a new recipe". We had to modify the RNEW card script as an exercise.
We only want to empty the fields when we go from the add button to the card. We need to leave what is there when returning from a sub card. This is easily achieved by setting a global variable used to flag the situation. We declare and set the flag in the Add Recipe button script. Thus, the Add Recipe button will now contain this script:
on mouseUp global REMPTY put "YES" into REMPTY go card "RNEW" end mouseUp
Now when we open the RNEW card each time we test its value, do the actions, then put NO into the flag. You can see the fields will only be emptied if the card is opened by the Add New Recipe button.
on openCard
global REMPTY
if REMPTY = "YES"
then
put empty into field "NEWRTYPE"
put empty into field "NEWRNAME"
put empty into field "NEWINGREDS"
put empty into field "NEWINSTRUCTS"
put empty into field "SERVES"
put empty into field "TTRTIME"
put empty into field "RNOTES"
end if
put "NO" into REMPTY
end openCard
I hope you all figured out something that did the same thing.
I also discovered a small bug in the rtypes.sh shell script. I used echo to return the values, which put them all in a line, not a list of one per line. This is easily fixed by getting rid of the echo variable and just printing the output of the command pipe, thus:
#!/bin/bash
#rtypes.sh
#
#Get recipe types from recipe book and echo the list
#This is the second field of the recipe book. The first field is an index no
#which we use to join to the multi line records stored in separate files
#
#set up a variable name for the recipe book folder
RBOOK="$HOME/cookbook/book"
#create the list. Print the second field, sort it and print unique lines
awk -F# '{print $2}' $RBOOK/recipes.txt | sort | uniq
In this HowTo we shall create the rest of the cards and compile it into a Stand Alone program.
This is the script for the select field.
on mouseDown
global SRTYPE
put the selectedText of me into SRTYPE
if SRTYPE <> empty
then
go card "RLIST"
end if
end mouseDown
This is the RLIST card script
on openCard global SRTYPE #Use this to list all the recipes with this type global COOK put (COOK & "/bin/trecipes.sh" && SRTYPE) into GETTRECS replace return with empty in GETTRECS put the shell of GETTRECS into RECLIST put RECLIST into field "RECIPELIST" end openCard
And this is the shell script trecipes.sh
#!/bin/bash
#trecipes.sh type
#
#Get recipes for types from recipe book and echo the names list with indexes
#Type is the second field of the recipe book.
#The name is the third field, Index is the first field
#
#set up a variable name for the recipe book folder
RBOOK="$HOME/cookbook/book"
RECTYPE=$1
#create the list. Select on the second field
awk -F# -v RTYPE=$RECTYPE '{ if ( $2 == RTYPE ) { print $1 " " $3 }}' $RBOOK/recipes.txt
This is a picture of the card RLIST after it has been opened with the above on openCard script.
This is the field script. The new command "put first word" saves the index of the recipe.
on mouseDown
global THISRECIPE
put the selectedText of me into THISRECIPE
if THISRECIPE <> empty
then
#get the index number
global RECIPEIDX
put first word of THISRECIPE into RECIPEIDX
go card "SHOWRECIPE"
end if
end mouseDown
This is the openCard script for the SHOWRECIPE card.
on openCard global RECIPEIDX global CBOOK global COOK put ( COOK & "/bin/getbit.sh" && RECIPEIDX) into GETBIT #set up the txt files put (CBOOK & "/recipes.txt") into MYRECS put (CBOOK & "/ingredients.txt") into MYINGS put (CBOOK & "/instructions.txt") into MYINSTS put (CBOOK & "/notes.txt") into MYNOTES #get the recipe components for this index number put the shell of (GETBIT && "3" && MYRECS) into field "TNAME" put the shell of (GETBIT && "4" && MYRECS) into field "TSERVES" put the shell of (GETBIT && "5" && MYRECS) into field "TTIME" put the shell of (GETBIT && "0" && MYINGS) into field "TINGREDS" put the shell of (GETBIT && "0" && MYINSTS) into field "TINSTRUCTS" put the shell of (GETBIT && "0" && MYNOTES) into field "TNOTES" end openCard
And this the shell script getbit.sh
#!/bin/bash
#getbit.sh indexnumber number file
#
#number will be greater than 2 if its the recipe file
#or 0 if its any other file
#get the record from the file
#
if [ $2 -gt 2 ]
then
#This is the recipe file
grep "$1" $3 | awk -F# -v MYFIELD=$2 '{print $MYFIELD}'
else
#A multi line file
#Only print those lines with greater than 2 characters
awk -v IDX=$1 'BEGIN{ RS="#" }{ if (NR == IDX){print $0}}' $3 | \
awk '{if (length($0) > 2 ) {print $0}}'
fi
This is what the card looks like. The blank square is for a picture which we shall do later.
On reflection I think the part of the ingredient that says MAIN is not necessary, so you can delete all this from your part 1 development if you like.
Now this bit is working, we can return to the start card and develop the search function.
This is the script for the Index Search button.
on mouseUp go card "RINDEX" end mouseUp
And this for the RINDEX card.
on openCard put empty into field "THISSEARCH" end openCard
This is the RINDEX card.
This is the script for the Look up in Recipes button.
on mouseUp ask "Enter a Single Word to Search for" put it into SLOOK global COOK global CBOOK put (CBOOK & "/recipes.txt") into RFILE put (COOK & "/bin/slook.sh" && SLOOK && RFILE) into THISLOOK replace return with empty in THISLOOK put the shell of THISLOOK into field "THISSEARCH" end mouseUp
The Look up in Ingredients button is the same except recipes.txt is replaced with ingredients.txt.
The shell script slook.sh works out what to return for each txt file.
#!/bin/bash
#slook.sh searchterm file
#
#Find out which file this is
THISFILE=`echo "$2" | grep "recipes" `
if [ ${#THISFILE} -gt 1 ]
then
#Its a recipe search
# do a case insensitive search and print the results
grep -i "$1" $2 | ( awk -F"#" '{ print $1 " " $3 }')
else
#Its an ingredients search
#Do a case insensitive search to find the record index numbers and use them to print the recipes
TINDEX=`awk -v STERM="$1" 'BEGIN{ IGNORECASE = 1 }{ if ( length($0) < 3) {IDX=$0} else \
if ( match ( $0, STERM ) > 0 ) {print IDX} }' $2`
for TINDX in $TINDEX
do
awk -F"#" -v IDX=$TINDX '{if($1 == IDX) print $1 " " $3 }' $HOME/cookbook/book/recipes.txt
done
fi
The ingredients part of this file is quite a clever script even though it is very short.
The first part sets the variable TINDEX to a list of recipe index numbers. The BEGIN section sets the Ignore Case option. It sets IDX if it finds a line with less than 3 characters, then the match function looks for the Search string in all the other lines greater than 3 characters, and if it finds one, prints its IDX value into the TINDEX variable.
Then for every value in the TINDEX variable it prints the index number and the name of the recipe.
That is quite an involved set of actions for such a simple script.
If you think you will have more than 999 recipes, increase the length function value to 4.
If you want to get rid of repeated lines you can save the output from the recipes file into a temporary file, and then cat that file and pipe it into sort followed by unique for the output, thus:
do
awk -F"#" -v IDX=$TINDX '{if($1 == IDX) print $1 " " $3 }' $HOME/cookbook/book/recipes.txt \
>> $HOME/cookbook/book/temp.txt
done
cat $HOME/cookbook/book/temp.txt | sort | uniq
This is probably a good idea if you do a lot of searches on common repetitive things like "cups", but generally speaking most ingredients only appear once in a recipe, so I left it out.
The list can be clicked to display the recipe just like from the type list, but we need to make sure that when we click the "Back" button on the recipe display it returns to this card.
Another global variable is set when we click the recipe name on this card which the recipe display back button can test.
This is the field script
on mouseDown
put the selectedText of me into GETTHIS
if GETTHIS <> empty
then
#Set a return to this page flag
global IDXPAGE
put "YES" into IDXPAGE
#Get the index number
global RECIPEIDX
put the first word of GETTHIS into RECIPEIDX
go card "SHOWRECIPE"
end if
end mouseDown
And this is the modified back button script on the SHOWRECIPE card:
on mouseUp
#Check for return to Index Search Page flag
global IDXPAGE
if IDXPAGE = "YES"
then
put "NO" into IDXPAGE
go card "RINDEX"
else
go card "RLIST"
end if
end mouseUp
If you've got this far without too much trouble, you can give yourself a big pat on the back, as you have dealt with all the programming concepts that most 3rd year students have paid for in time and sweat. And its only taken you less than 3 months. Of course your advantage is that you are developing and implementing real world applications, which always seems to be an easier learning path, as there is a good deal of meaning in all the madness.
The final steps in this HowTo are to add a picture for each recipe and while we are doing that, clean up the add recipe cards, getting rid of unwanted features and making sure everything works as planned.
I have shrunk the recipe instructions field up a bit to make room for an "Add Picture" button and a thumbnail picture view. Clicking the button goes to another card created with the name ADDPIC.
on mouseUp go card "ADDPIC" end mouseUp
The new Add Recipe card looks like this:
The new card looks like this:
Well, I didn't have a nice recipe picture ready, so I thought you might like to see my Xmas TUX mascot.
These are the scripts for the card:
on openCard put empty into field "CURRDIR" set the filename of image "RPIC" to empty #Start looking in the HOME folder set the defaultFolder to $HOME end openCard
Notice the defaultFolder statement. This sets the default folder to your home directory as you have probably got your pictures saved somewhere inside it.
This is the Select Folder to View button:
on mouseUp
answer folder "Please choose a folder to view"
global FCHOSEN
put it into FCHOSEN
put the shell of ("ls" && FCHOSEN) into field "CURRDIR"
end mouseUp
You have already seen what happens with the "answer folder" function in the file zip utility, but if you didn't implement that HowTo, it allows you to double click around the whole file tree looking for a likely source of pictures. You can either allow the folder name to be selected or enter the folder and click OK.
This is the script for the field of file names:
on mouseDown global FCHOSEN global PICPATH #set the default Folder so that the chosen picture can be displayed set the defaultFolder to FCHOSEN put the selectedText of me into PICPATH set the filename of image "RPIC" to PICPATH end mouseDown
The default folder is reset to the current one so that RunRev will "see" the picture.
The chosen image is assigned to the image field using the "set filename" statement.
This is the Save this Pic button script:
on mouseUp
global PICPATH
global FCHOSEN
#set up the target image path
put ( $HOME & "/cookbook/book/images/" & PICPATH ) into RIMAGE
replace return with empty in RIMAGE
#Set up the source image path
put (FCHOSEN & "/" & PICPATH) into SIMAGE
replace return with empty in SIMAGE
#Set up a shell script to copy the chosen file to the cookbook
put ("cp" && SIMAGE && RIMAGE) into myshell
replace return with empty in myshell
put the shell of myshell into DUNCP
#Put the saved image into the thumbnail
set the filename of image "PICT" of card "RNEW" to RIMAGE
#Return to the calling card
go card "RNEW"
end mouseUp
Make sure you create the folder called cookbook/book/images so that the image can be saved there.
The discard button is just a return to the calling card like all the other discard button scripts.
on mouseUp go card "RNEW" end mouseUp
Now we need to modify the end of the script for the SAVE RECIPE button.
After the close file NFILE and the associated end if insert these lines up to the ###### line.
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
####### end of picture file insert
end if
end if
go card "RTSELECT"
end mouseUp
Create the file cookbook/book/images.txt with the command:
touch $HOME/cookbook/book/images.txt
This is all that has to be done to save the images and associated information. We have to modify the Show Recipe scripts, but first we'll clean up the Ingredients card. Your modified card should look like this:
And the SAVE script is like this:
on mouseUp
global NINGMEAS
global NINGNAME
global NINGQUANT
if NINGMEAS <> empty and NINGNAME <> empty and NINGQUANT <> empty
then
if NINGMEAS = "NUMBER"
then
put space 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"
end if
go card "RNEW"
end mouseUp
You can use a text editor to delete the relevant information in the ingredients.txt. Editing your recipes could be a good added feature. I know I quite often substitute ingredients and make notes in recipe books, so I'll add this feature for part 3 of this HowTo.
This is the new script for the SHOWRECIPE card:
on openCard global RECIPEIDX global CBOOK global COOK put ( COOK & "/bin/getbit.sh" && RECIPEIDX) into GETBIT #set up the txt files put (CBOOK & "/recipes.txt") into MYRECS put (CBOOK & "/ingredients.txt") into MYINGS put (CBOOK & "/instructions.txt") into MYINSTS put (CBOOK & "/notes.txt") into MYNOTES put (CBOOK & "/images.txt") into MYPICS #get the recipe components for this index number put the shell of (GETBIT && "3" && MYRECS) into field "TNAME" put the shell of (GETBIT && "4" && MYRECS) into field "TSERVES" put the shell of (GETBIT && "5" && MYRECS) into field "TTIME" put the shell of (GETBIT && "0" && MYINGS) into field "TINGREDS" put the shell of (GETBIT && "0" && MYINSTS) into field "TINSTRUCTS" put the shell of (GETBIT && "0" && MYNOTES) into field "TNOTES" put the shell of (GETBIT && "99" && MYPICS) into THISPIC #Get rid of any carriage returns at the end of the file name replace return with empty in THISPIC set the filename of image "TPIC" to THISPIC end openCard
And this is the new getbit.sh shell script. I've changed things bit to make sure that only the line relative to the index number is processed for the recipe.
#!/bin/bash
#getbit.sh indexnumber number file
#
#number will be greater than 2 and less than 99
#if its the recipe file
#if it is 99 it is a picture
#or 0 if its any other file
#get the record from the file
#
if [ $2 -gt 2 -a $2 -lt 99 ]
then
#This is the recipe file. Process the index number line
THISBIT=`awk -F"#" -v IDX=$1 '{ if (NR == IDX) print $0}' $3 |\
awk -F# -v MYFIELD=$2 '{print $MYFIELD}' `
echo $THISBIT
else
#A multi line file
#Only print those lines with greater than 2 characters
if [ $2 -eq 0 ]
then
awk -v IDX=$1 'BEGIN{ RS="#" }{ if (NR == IDX){print $0}}' $3 |\
awk '{if (length($0) > 2 ) {print $0}}'
else
if [ $2 -eq 99 ]
then
#the picture
#print second field for the index number line
awk -F"#" -v IDX=$1 '{ if (NR == IDX) print $2}' $3
fi
fi
fi
Finally, go round all the fields that do not get information typed directly into them and use the property manager to lock the text. Go round all images and set the position property to lock location and size.
The last step is to save your work as a standalone application and see if it works. After that you can go back and colour in those bits you want, change a few font sizes to make it more readable and repeat the standalone save.
Here is a recipe for all the blokes who wish to impress their girlfriends:
New Year Cheese Scones
Ingredients:
2 cups flour2 teaspoons baking powder
2 oz butter
1/2 teaspoon salt, pinch of cayenne pepper
3 oz. grated cheese
milk to mix to a fairly firm dough
Method:
1. Mix together flour, baking powder, salt and pepper.
2. Cut in the butter: add milk and mix just enough to incorporate.
3. On a floured board, roll out to 1/2 inch thick.
4. Cut into rounds and place on greased cookie sheet.
5. Brush tops with milk and sprinkle with some more grated cheese.
6. Cook at 400º Fahrenheit for 10 minutes.
7. Cool on a wire tray under cloth.
Novell Cool Solutions (corporate web communities) are produced by WebWise Solutions. www.webwiseone.com
Learning to use Linux at Home and Work