Novell Home

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

Novell Cool Solutions: Feature
By Stomfi

Digg This - Slashdot This

Posted: 15 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 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 flour
2 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.

next HowTo: Part 3


Novell Cool Solutions (corporate web communities) are produced by WebWise Solutions. www.webwiseone.com

© 2014 Novell