We want to be able to get a simple list of the records in our database. The function list_records just goes through the mapping records and puts the indices, i.e. the record names, in an array of strings, record_names. By using the builtin function sort we put the record names into the array in alphabetical order which might be a nice touch. For the printout we just print a header, "Records:", followed by a newline. Then we use the loop control structure for to traverse the array and print every item in it, including the number of the record, by counting up from zero to the last item of the array. The builtin function sizeof gives the number of items in an array. The printout is formatted through the use of sprintf which works more or less like the C function of the same name.#!/usr/local/bin/pike
mapping (string:array(string)) records =
([
"Star Wars Trilogy" : ({
"Fox Fanfare",
"Main Title",
"Princess Leia's Theme",
"Here They Come",
"The Asteroid Field",
"Yoda's Theme",
"The Imperial March",
"Parade of the Ewoks",
"Luke and Leia",
"Fight with Tie Fighters",
"Jabba the Hut",
"Darth Vader's Death",
"The Forest Battle",
"Finale"
})
]);
If the command line contained a number our program will find the record of that number and print its name along with the songs of this record. First we create the same array of record names as in the previous function, then we find the name of the record whose number (num) we gave as an argument to this function. Next we put the songs of this record in the array songs and print the record name followed by the songs, each song on a separate line.void list_records()
{
int i;
array (string) record_names=sort(indices(records));
write("Records:\n");
for(i=0;i<sizeof(record_names);i++)
write(sprintf("%3d: %s\n", i+1, record_names[i]));
}
The main function doesn't do much; it checks whether there was anything on the command line after the invocation. If this is not the case it calls the list_records function, otherwise it sends the given argument to the show_record function. When the called function is done the program just quits.void show_record(int num)
{
int i;
array (string) record_names = sort(indices (records));
string name=record_names[num-1];
array (string) songs=records[name];
write(sprintf("Record %d, %s\n",num,name));
for(i=0;i<sizeof(songs);i++)
write(sprintf("%3d: %s\n", i+1, songs[i]));
}
int main(int argc, array (string) argv)
{
if(argc <= 1)
{
list_records();
} else {
show_record((int) argv[1]);
}
}
2.1.1 add_record()
Using the method Stdio.Readline()->read() we wait for input which will be put into the variable record_name. The argument to ->read() is printed as a prompt in front of the user's input. Readline takes everything up to a newline character.
Now we use the control structure while to check whether we should continue inputting songs.
The while(1) means "loop forever", because 1 is always true.
This program does not in fact loop forever, because it uses return
to exit the function from within the loop when you type a period.
When something has been read into the variable song it is checked.
If it is a "." we return a null value that will be used in the while statement to indicate that it is not ok to continue asking for song names.
If it is not a dot, the string will be added to the array of songs for this record, unless it's an empty string.
Note the += operator. It is the same as saying
records[record_name]=records[record_name]+({song}).
void add_record()
{
string record_name=Stdio.Readline()->read("Record name: ");
records[record_name]=({});
write("Input song names, one per line. End with '.' on its own line.\n");
while(1)
{
string song;
song=Stdio.Readline()->read(sprintf("Song %2d: ",
sizeof(records[record_name])+1));
if(song==".")
return;
if (strlen(song))
records[record_name]+=({song});
}
}2.1.2 main()
The main function now does not care about any command line arguments.
Instead we use Stdio.Readline()->read() to prompt the user for instructions
and arguments. The available instructions are "add", "list" and "quit".
What you enter into the variables cmd and args is checked in the
switch() block. If you enter something that is not covered
in any of the case statements the program just silently ignores it and
asks for a new command.
In a switch() the argument (in this case cmd) is checked in the case statements. The first case where the expression equals cmd then executes the statement after the colon. If no expression is equal, we just fall through without any action.
The only command that takes an argument is "list" which works as in the first version of the program.
If "list" receives an argument, that record is shown along with all the songs
on it. If there is no argument it shows a list of the records in the database.
When the program returns from either of the listing functions, the break instruction tells the program to jump out of the switch() block.
"add" of course turns control over to the function described above.
If the command given is "quit" the exit(0) statement stops the execution of the program and returns 0 (zero) to the operating system, telling it that everything was ok.
int main(int argc, array(string) argv)
{
string cmd;
while(cmd=Stdio.Readline()->read("Command: "))
{
string args;
sscanf(cmd,"%s %s",cmd,args);
switch(cmd)
{
case "list":
if((int)args)
{
show_record((int)args);
} else {
list_records();
}
break;
case "quit":
exit(0);
case "add":
add_record();
break;
}
}
}
2.2.1 save()
First we clone a Stdio.File program to the object o.
Then we use it to open the file whose name is given in the string file_name for writing.
We use the fact that if there is an error during opening, open() will return a false value which we can detect and act upon by exiting.
The arrow operator (->) is what you use to access methods and variables in an object.
If there is no error we use yet another control structure, foreach, to go through the mapping records one record at a time.
We precede record names with the string "Record: " and song names with "Song: ".
We also put every entry, be it song or record, on its own line by adding a newline to everything we write to the file.
Finally, remember to close the file.
void save(string file_name)
{
string name, song;
Stdio.File o=Stdio.File();
if(!o->open(file_name,"wct"))
{
write("Failed to open file.\n");
return;
}
foreach(indices(records),name)
{
o->write("Record: "+name+"\n");
foreach(records[name],song)
o->write("Song: "+song+"\n");
}
o->close();
}2.2.2 load()
The load function begins much the same, except we open the file named file for reading instead.
When receiving data from the file we put it in the string file_contents.
The absence of arguments to the method o->read means that the reading should not end until the end of the file.
After having closed the file we initialize our database, i.e. the mapping records. Then we have to put file_contents into the mapping and we do this by splitting the string on newlines (cf. the split operator in Perl) using the division operator. Yes, that's right: by dividing one string with another we can obtain an array consisting of parts from the first. And by using a foreach statement we can take the string file_contents apart piece by piece, putting each piece back in its proper place in the mapping records.
void load(string file_name)
{
string name="ERROR";
string file_contents,line;
Stdio.File o=Stdio.File();
if(!o->open(file_name,"r"))
{
write("Failed to open file.\n");
return;
}
file_contents=o->read();
o->close();
records=([]);
foreach(file_contents/"\n",line)
{
string cmd, arg;
if(sscanf(line,"%s: %s",cmd,arg))
{
switch(lower_case(cmd))
{
case "record":
name=arg;
records[name]=({});
break;
case "song":
records[name]+=({arg});
break;
}
}
}
}2.2.3 main() revisited
main() remains almost unchanged, except for the addition of two case statements with which we now can call the load and save functions. Note that you must provide a filename to load and save, respectively, otherwise they will return an error which will crash the program.
case "save":
save(args);
break;
case "load":
load(args);
break;
2.3.1 delete()
If you sell one of your records it might be nice to able to delete that entry from the database. The delete function is quite simple.
First we set up an array of record names (cf. the list_records function).
Then we find the name of the record of the number num and use the builtin function m_delete() to remove that entry from records.
void delete_record(int num)
{
array(string) record_names=sort(indices(records));
string name=record_names[num-1];
m_delete(records,name);
}2.3.2 search()
Searching for songs is quite easy too. To count the number of hits we declare the variable hits. Note that it's not necessary to initialize variables, that is done automatically when the variable is declared if you do not do it explicitly. To be able to use the builtin function search(), which searches for the presence of a given string inside another, we put the search string in lowercase and compare it with the lowercase version of every song. The use of search() enables us to search for partial song titles as well.
When a match is found it is immediately written to standard output with the record name followed by the name of the song where the search string was found and a newline.
If there were no hits at all, the function prints out a message saying just that.
void find_song(string title)
{
string name, song;
int hits;
title=lower_case(title);
foreach(indices(records),name)
{
foreach(records[name],song)
{
if(search(lower_case(song), title) != -1)
{
write(name+"; "+song+"\n");
hits++;
}
}
}
if(!hits) write("Not found.\n");
}2.3.3 main() again
Once again main() is left unchanged, except for yet another two case statements used to call the search() and delete functions, respectively. Note that you must provide an argument to delete or it will not work properly.
case "delete":
delete_record((int)args);
break;
case "search":
find_song(args);
break;
|