HowTo: Create a Home Cooks Recipe Book using the Linux Shell and RunRev GUI Part 3
Novell Cool Solutions: Feature
By Stomfi
|
Digg This -
Slashdot This
Posted: 22 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 third part we develop the edit recipe parts of the system. In the fourth and last part we will develop the shopping list system.
This is a view of the recipe card for Tasmanian Prawns with American Pecan Pesto including picture.
You can see I've added two more buttons to this card. Edit Recipe and Shopping List.
Their scripts take you to two other cards. These cards can take you to further cards so we set of another of those flag names to make sure we only repopulate the card when we go there for the first time.
on mouseUp global FIRSTETIME put "YES" into FIRSTETIME go card "REDIT" end mouseUp
on mouseUp global FIRSTSTIME put "YES" into FIRSTSTIME go card "SHOP" end mouseUp
At this point I'd like to say that although this is a recipe application, the methods used here can be applied to any business situation, for example shopping lists in purchasing, where supplied quantities are usually larger than what is needed, or one off repeatable procedures and event orders for office parties or shows or even ad-hoc project planning. A bit of lateral thinking and any of these office/factory/home applications can be modified to suit all sorts of different situations. And with your new knowledge that you get from these HowTos, you are going to be the most valued person where ever you are.
Of course you'll be thinking outside the normal boundaries expected from your old MS windows centric IT management section, but Linux is all about the right to choose how You want to do things, so you'll have to spend a bit of time re-educating them. Be cool, the benefits are unlimited.
People are always talking about a killer application for Linux that will let it by pass MS, but you are learning that it is the application of Linux that is the MS killer. As I tell my Digital Divide students "Linux is More". I hope you are learning, as they do, how much more it is.
A note on the use of global names. Sometimes I have found the program doesn't seen to pick up on these where I've used them for paths. If you have this trouble, explicitly name the path. e.g. I use the global name CBOOK for $HOME & "/cookbook/bin". If CBOOK doesn't work, use the longer definition starting from $HOME.
First, we shall develop the REDIT card. This is the card which is populated exactly like the Show Recipe card.
This is the card script:
on openCard
global FIRSTETIME
if FIRSTETIME <> "NO"
then
put field "TNAME" of card "SHOWRECIPE" into field "ENAME"
put field "TSERVES" of card "SHOWRECIPE" into field "ESERVES"
put field "TTIME" of card "SHOWRECIPE" into field "ETIME"
put field "TINGREDS" of card "SHOWRECIPE" into field "EINGREDS"
put field "TINSTRUCTS" of card "SHOWRECIPE" into field "EINSTRUCTS"
put field "TNOTES" of card "SHOWRECIPE" into field "ENOTES"
put the filename of image "TPIC" of card "SHOWRECIPE" into PICFILE
set the filename of image "EPIC" to PICFILE
#Set the first time flag to "NO"
put "NO" into FIRSTETIME
end if
end openCard
This is the Ingredients field script:
on mouseDown
put the selectedText of me into EINGLINE
#Save its Line number
put the hilitedLine of me into ELINE
put ("Modify" && EINGLINE && "to:") into THISASK
ask THISASK
put it into ENING
if ENING <> empty
then
#Use the index number
global RECIPEIDX
global COOK
put (COOK &"/bin/eding.sh" && RECIPEIDX && ELINE && quote & ENING & quote) into CLINE
replace return with empty in CLINE
put the shell of CLINE into field "EINGREDS"
end if
end mouseDown
This is the eding.sh shell script:
#!/bin/bash
#eding.sh indexno lineno newline
#replace lineno for indexno in ingredients.txt with newline
INGFILE="$HOME/cookbook/book/ingredients.txt"
CTMP="$HOME/cookbook"
if [ $1 -lt 1 ]
then
exit
fi
#First select all the ingredients for this recipe index
#to find out which line to replace
#The first part of this 3 stage awk pipe prints all the lines for the index recipe.
#The second part uses the output of the first part and prints lines with more than 3 characters
#The last part uses the lineno to print only that line, and saves it to a temporary file.
awk -v IDX="$1" 'BEGIN{RS = "#"}{if(NR == IDX){print $0}}' $INGFILE |\
awk '{if(length() > 3)print $0}' |\
awk -v LINNO="$2" '{if(NR == LINNO) print $0}' > $CTMP/thisone.txt
#
#Copy the line back into a variable for use in the grep command.
CLINE=`cat $CTMP/thisone.txt`
#
#Find the line number in the original file, using the line saved from the awk pipe.
#Grep prints the line number followed by a ":" and the text line. Awk prints just the number.
CLINNO=`grep -n -E "$CLINE" $INGFILE | awk -F":" '{print $1}'`
#
#Copy the file to a temporary one
cp $INGFILE $CTMP/ingreds.txt
#
#Use sed to insert the new line then delete the old line
#Notice all the quotes. That is so sed can work out which are shell variable to be expanded
# each "-e" is followed by a sed action. The first inserts a line, the second deletes a line.
sed -e "$CLINNO"i"$3" -e "$CLINNO"d $CTMP/ingreds.txt > $CTMP/ingredients.txt
#
#Check to see if all went OK ie the file is not empty
if [ -s $CTMP/ingredients.txt ]
then
#Copy the file back to the original
cat $CTMP/ingredients.txt > $INGFILE
#
#Now we use the getbit.sh script to refill the edit card field
$HOME/cookbook/bin/getbit.sh "$1" "0" $INGFILE
fi
I am always impressed by the ability of the shell tools to perform such complex tasks in such an easy and understandable manner. I had to look up the info pages for grep and sed to figure out how to do all this. The awk stuff repeats things we have already covered in previous HowTos.
This is the ENAME field script:
on mouseDown
put field "ENAME" into CNAME
put ("Change" && CNAME && "To:") into NMSG
ask NMSG
put it into EDNAM
if EDNAM <> empty
then
global RECIPEIDX
global COOK
put (COOK & "/bin/edrname.sh" && RECIPEIDX && EDRNAME) into EDNAM
replace return with empty in EDNAM
put the shell of EDNAM into field "ENAME"
end if
end mouseDown
The ESERVES and ETIME field scripts are the same with obvious name changes.
This is the edrname.sh shell script:
#!/bin/bash
#edrname.sh indexno newname
#change recipe name to newname
RFILE="$HOME/cookbook/book/recipes.txt"
CTMPFILE="$HOME/cookbook/recip.txt"
#
#If line is the indexed recipe change its third field to new name
#Set the Output Field Separator(OFS) to "#"
#Put the result into a temporary file
awk -F"#" -v IDX="$1" -v NEWN="$2" 'BEGIN{OFS = "#"}\
{{if($1 == IDX){$3 = NEWN}}{print $0}}' $RFILE > $CTMPFILE
#
#Check that there is something in the temporary file
if [ -s $CTMPFILE ]
then
#update the original recipes file
cat $CTMPFILE > $RFILE
#Send the new name back
echo "$2"
fi
The field scripts and shell scripts for the serves and time will be the same with obvious changes, importantly the name of the script, and the field number to equal NEWN. I'll let you create these without any help.
These are the ENOTES field scripts which are similar to the ones for Ingredients, except they contain a provision to add a new note, which you do by clicking on a blank line in the field. You can add this feature to the ingredients edit scripts if you like. I like to keep the original as is, and add notes for extras or changes.
on mouseDown
put the selectedText of me into CNOTE
put the hilitedLine of me into CLINE
if CNOTE <> empty
then
put ("Modify" && CNOTE && "To:") into CASK
put "0" into TYPE
ask CASK
put it into ENOTE
else
put "1" into TYPE
ask "Enter a new note"
put it into ENOTE
end if
if ENOTE <> empty
then
global COOK
global RECIPEIDX
put (COOK & "/bin/edrnote.sh" && RECIPEIDX && CLINE && quote & ENOTE & quote && TYPE) into EDRNOTE
replace return with empty in EDRNOTE
put the shell of EDRNOTE into field "ENOTES"
end if
end mouseDown
The edrnote.sh contains the script lines for adding a new line to the existing record. You can use these types of lines in other applications where you need to add lines to the end of records.
#!/bin/bash
#edrnote.sh indexno lineno newline type
#replace lineno for indexno in notes.txt with newline
NOTEFILE="$HOME/cookbook/book/notes.txt"
CTMP="$HOME/cookbook"
if [ $1 -lt 1 ]
then
exit
fi
INDEX=$1
LINENO=$2
NLINE="$3"
TYPE=$4
if [ $TYPE -lt 1 ]
then
#This is a modification line
#First select all the notes for this recipe index
#to find out which line to replace
awk -v IDX="$INDEX" 'BEGIN{RS = "#"}{if(NR == IDX){print $0}}' $NOTEFILE |\
awk '{if(length() > 3) print $0}'|\
awk -v LINNO="$2" '{if(NR == LINNO) print $0}' > $CTMP/thisone.txt
CLINE=`cat $CTMP/thisone.txt`
#This is a debug statement. You shouldn't really get this error
if [ ${#CLINE} -lt 1 ]
then
echo "Couldn't find line $2"
exit
fi
#Now we find the line number in the original file
CLINNO=`grep -n -E "$CLINE" $NOTEFILE | awk -F":" '{print $1}'`
#Now we use sed to insert the new line then delete the old line
cp $NOTEFILE $CTMP/newnotes.txt
sed -e "$CLINNO"i"$NLINE" -e "$CLINNO"d $CTMP/newnotes.txt > $CTMP/notes.txt
else
#Add a new line to the notes file for this recipe
#First select all the notes for this recipe index
#and print the last note line to find out where the insert point is
awk -v IDX="$INDEX" 'BEGIN{RS = "#"}{if(NR == IDX){print $0}}' $NOTEFILE |\
awk '{if(length() > 3) NLINE = $0}END{print NLINE}' > $CTMP/thisone.txt
CLINE=`cat $CTMP/thisone.txt`
#Now we find the line number in the original file
CLINNO=`grep -n -E "$CLINE" $NOTEFILE | awk -F":" '{print $1}'`
#Now we use sed to insert the new line
cp $NOTEFILE $CTMP/newnotes.txt
sed -e "$CLINNO"i"$NLINE" $CTMP/newnotes.txt > $CTMP/notes.txt
fi
if [ -s $CTMP/notes.txt ]
then
cat $CTMP/notes.txt > $NOTEFILE
#Now we use the getbit.sh script to refill the field
$HOME/cookbook/bin/getbit.sh "$1" "0" $NOTEFILE
fi
You can see the second part is almost the same. The awk pipe is reduced to two stages and the note line is saved into a variable NLINE, The END awk statement prints the last one saved, which is the line before the new entry. The sed command only inserts the new note.
The awk END statement is the only new element in this script. You can find out more about BEGIN and END in the on line awk documentation.
This is the EINSTRUCTS field script:
on mouseDown
answer "Do you with to edit instructions" with "Yes" or "No"
put it into YANS
if YANS = "Yes"
then
put field "EINSTRUCTS" into field "NEWINSTRUCTS" of card "INSEDIT"
go card "INSEDIT"
end if
end mouseDown
This is the card:
This is the Save Changes button script:
on mouseUp global RECIPEIDX global COOK put field "NEWINSTRUCTS" into EDINSTR put (COOK & "/newinstructs.txt") into IFILE open file IFILE for write write EDINSTR to file IFILE close file IFILE put (COOK & "/bin/edrinstr.sh" && RECIPEIDX) into EDRINSTR replace return with empty in EDRINSTR put the shell of EDRINSTR into field "NEWINSTRUCTS" put field "NEWINSTRUCTS" into field "EINSTRUCTS" of card "REDIT" go card "REDIT" end mouseUp
The instruction field is saved to the temporary file "newinstructs.txt" to preserve its format. If we just included it as a shell argument it will be treated as if it is one line without line breaks.
And this is the edrinstr.sh shell script:
#!/bin/bash
#edrinstr.sh indexno
#replace instructions for indexno in instructions.txt with new instructions
#in file newinstructs.txt
INSFILE="$HOME/cookbook/book/instructions.txt"
NINSFILE="$HOME/cookbook/newinstructs.txt"
CTMP="$HOME/cookbook"
if [ $1 -lt 1 ]
then
exit
fi
#Print all the instructions replacing the one for this recipe index
#Set the Field Separators (FS & OFS) to a newline
#The special getline function reads the lines in the NINSFILE
awk -v IDX="$1" -v NEWFILE="$NINSFILE" 'BEGIN{RS = "#";FS = "\n";OFS = "\n"};\
{if(NR != IDX) print $0 "\n#"; else\
{if(NR == IDX) {getline < NEWFILE; print NR "\n" $0 "\n#" }}}' $INSFILE > $CTMP/instructs.txt
#get rid of the extra # in the last three lines
#This is a fix for the awk script. Awk experts please send a solution
NUMLINES=`wc $CTMP/instructs.txt | awk '{print $1}'`
#Minus 3 lines
let NUMLINES=$NUMLINES-3
#Print the first NUMLINES
head -"$NUMLINES" $CTMP/instructs.txt > $CTMP/instructions.txt
#Check the file is not empty
if [ -s $CTMP/instructions.txt ]
then
cat $CTMP/instructions.txt > $INSFILE
#Now we use the getbit.sh script to refill the field
$HOME/cookbook/bin/getbit.sh "$1" "0" $INSFILE
fi
In this shell script the awk program is used to read in the newinstructs.txt file replacing the record that is equal to the recipe index. If the record is not that one it prints it terminated by a new line and a #. If it is it has to put the recipe index number back as well. The input and output field separators are set to a new line. My awk script prints out an extra 3 lines terminated by a # so The next part of the shell script gets rid of those using the "head" shell tool, which prints out all but the last 3.
You'll notice I used a few semi colons in the awk script. The if ; else won't work without one, and the others are to separate actions within brackets. A bit of awk syntax.
This is the Image field script:
on mouseDown go card "PICEDIT" end mouseDown
And this is the card, which is very similar to the Add Picture card.
This is the Save This Picture button. The others are the same as the Add Picture ones. The Discard button script returns to the "REDIT" card.
This script checks to see if our source image is not already in the cookbook/book/images folder. You can add this bit to the Add Picture card if you like to put your new scanned in pictures there.
on mouseUp
global PICPATH
global FCHOSEN
#Set up the cookbook image folder
put ($HOME & "/cookbook/book/images") into CIMAGES
replace return with empty in CIMAGES
#Check that the source image is not already in the cookbook image folder
if FCHOSEN <> CIMAGES
then
#set up the target image path
put ( CIMAGES & "/" & 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
end if
#Change the images.txt reference
global COOK
global RECIPEIDX
put (COOK & "/bin/edrpic.sh" && RECIPEIDX && RIMAGE) into EDPIC
replace return with empty in EDPIC
put the shell of EDPIC into DUNTHIS
set the filename of image "EPIC" of card "REDIT" to RIMAGE
#Return to the calling card
go card "REDIT"
end mouseUp
This is the edrpic.sh shell script:
#!/bin/bash
#edrpic.sh indexno newpic
#change pic name to newpic
PFILE="$HOME/cookbook/book/images.txt"
CTMPFILE="$HOME/cookbook/imgs.txt"
#
#If line is the indexed recipe change its second field to new name
#Set the Output Field Separator(OFS) to "#"
#Put the result into a temporary file
awk -F"#" -v IDX="$1" -v NEWN="$2" 'BEGIN{OFS = "#"}\
{{if($1 == IDX){$2 = NEWN}}{print $0}}' $PFILE > $CTMPFILE
#
#Check that there is something in the temporary file
if [ -s $CTMPFILE ]
then
#update the original images file
cat $CTMPFILE > $PFILE
fi
These different methods give you a good idea how to do the same thing in different ways depending on the circumstances or your personal preferences. Editing existing records is probably one of the most complicated things we can do. You can see that in writing these shell scripts, you can perform complex actions without having to learn anything more complicated than a few simple shell tools to do it. The awk tool is the most complicated one and I have shown you how to use it in its most simple ways. Real programmers will probably throw their arms up in horror at my scripts, but we are ordinary home, factory and office workers, who want to do things for ourselves in any simple way as long as it works. And with Linux tools and Runtime Revolution, we can.
In the next (part 4) and final part (part 5) of this application we will develop 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 a field to the Add Ingredients card in the Add Recipe system to do this. Packages can be fixed or not depending if we can get arbitrary quantities from the delicatessen.
I hope you like my Tiger Prawns recipe. I just cooked it for dinner to try it out. Scrumptious.
Novell Cool Solutions (corporate web communities) are produced by WebWise Solutions. www.webwiseone.com

Learning to use Linux at Home and Work