MovieMan – first steps

A lot of features have been covered in this chapter, but only a small handful are needed for the MovieMan module we're about to implement. We'll put more of them to use as we add to the project in later chapters. Throughout the book, the full path of any example source module is referred to as $LEARNINGD/ChapterNum/filename.d. This is impractical for the MovieMan code listings, as the project will be implemented across multiple chapters. When referring to MovieMan source modules, the form $MOVIEMAN/filepath/filename.d will be used, where $MOVIEMAN is the root directory of either the DUB project we created in the first chapter or of any other location you choose to initialize the MovieMan project with DUB.

The io module

Create an empty file in your text editor and save it as $MOVIEMAN/source/movieman/io.d. This module will consist of a few module-scope functions that read text from standard input and print text to standard output. We're going to implement the entire module in this chapter so that we don't need to revisit it later on. We'll start with the module declaration and an import of std.stdio as the first two lines.

module movieman.io;
import std.stdio;

There are two primary forms of input that MovieMan requires. strings are used to input movie titles and uints are for case numbers, page numbers, and menu commands. To read strings, we'll implement readString, the primary workhorse of the io module.

string readString() {
  import std.string : chomp;
  return readln().chomp;
}

readString calls two functions that we haven't seen. std.stdio.readln is what does the work of reading a line of text from stdin. It returns a string containing the text along with the platform-specific line terminator. Retaining the line terminator would cause extra work later on to avoid breaking the format of the program's output, so we turn to std.string.chomp to handle it here instead. This function can be given any delimiter, which it will look for at the end of a string. It the delimiter is found, it returns the string without the delimiter; otherwise, it returns the original string. When no delimiter is specified, it looks for several non-text characters. Refer to http://dlang.org/phobos/std_string.html#.chomp for details.

The next function will be readUint. It takes a string returned from readString and parses it for a uint value via std.conv.to. Recall from the discussion of strings that to throws an exception if the string cannot be converted. Any number that the user types in will represent either a case number, a page number, or a menu command, none of which can ever be 0. To handle the exception, we can make use of a feature that we won't discuss until the next chapter, though it's one that many readers will be familiar with: the try…catch block. If to throws an exception, we can catch it and simply return 0.

uint readUint() {
  import std.conv : to;
  try {
    return readString().to!uint;
  }
  catch(Exception e) {
    return 0;
  }
}

readString and readUint are the building blocks of the rest of the module. The first higher-level function we'll implement is called readTitle. It asks the user to enter a movie title, then calls readString to get the input, which it immediately returns.

string readTitle() {
  writeln("
Enter a movie title:");
  return readString();
}

There's no check to see if we have an empty string here. This will be handled at a higher level in the program. Next is a function we'll call readNumber. It asks the user to enter a case or page number, then calls readUint to read the input. The function takes one argument, a string whose value should either be "case" or "page", which it inserts into the output.

uint readNumber(string label) {
  writefln("
Enter a %s number:", label);
  return readUint();
}

Next, we have a pair of overloaded functions that will be used when a user needs to choose one or more actions, both named readChoice. The functions display one or more options, asking the reader to choose one or to press Enter for the default option. These functions will only be called in specific circumstances, such as when the user has made a selection from the main menu and needs to decide on how to proceed. The default option can be defined as aborting the current action. No action that moves the program forward or makes a change in the program state should be the default. The name of the default option is specified in the second parameter, the default value of which is "abort".

bool readChoice(string msg1, string msg2 = "abort") {
  writefln("
Enter 1 to %s.", msg1);
  writefln("Press Enter to %s.", msg2);
  return readUint() == 1;
}
uint readChoice(string[] msgs, string msg2 = "abort") {
  writeln();
  foreach(i, msg; msgs)
    writefln("Enter %s to %s.", i+1, msg);
  writefln("Press Enter to %s.", msg2);
  return readUint();
}

When the user types any input, she must press Enter in order for readln to pick it up. If Enter is pressed without any input, the line returned from readString will only contain a line terminator. This will cause an exception to be thrown inside readUint, which will catch it and return 0. The downside is that there is no way to tell if the user simply pressed Enter or typed in a bunch of gibberish. We could parse the input string to see if it only contains a line terminator, or use the operating system API to check if the Enter key was pressed, but there's really no reason to do so. Since the default option in any call to readChoice is to abandon whatever action is in progress, it doesn't matter what the input is, if it wasn't one of the possible options. On invalid input, the program can go back to a previous screen and the user can start again if a mistake was made.

We could conceivably do without the first version of readChoice, as the second one can handle a single action just fine. But this is a good opportunity to demonstrate one aspect of thinking in D. The second version takes an array as the first parameter. Using only one option with this function would require one of the following approaches:

readChoice(["Option"]);      // Approach 1
string[1] option = ["Option"];  // Approach 2
readChoice(option);

The first approach allocates a dynamic array on the GC heap every time it is executed. A D programmer should always be aware of which language features are allocating GC memory. For a simple little project like MovieMan, this isn't likely to ever be an issue, but readChoice could still be called numerous times in a normal run of the program. It isn't going to hurt to avoid gratuitous allocations from the GC heap. The second approach avoids the repeated allocation, but (for me, at least) it's just plain annoying to use a single-element array for what really should be a single variable.

The final function in the io module prints a header, followed by a list of numbered options. In the next chapter, we'll use this function to print menus.

void printList(string header, string[] list) {
  writeln("
", header);
  foreach(i, line; list)
    writefln("	%s. %s", i+1, line);
  writeln();
}

On the surface, there doesn't appear to be much difference in the output of readChoice and printList, but they are used in different contexts. In the next chapter, we'll implement a class called Menu that manages its own array of options and uses printList simply for display. The format of the output is different between the two functions as a visual cue to distinguish between primary menus and submenus.

The app module

The file $MOVIEMAN/source/app.d was generated by DUB when the project was initialized. Open this file and replace its contents with the following code:

import std.stdio;
import movieman.io;
void main() {
  auto title = readTitle();
  writeln("The title is: ", title);

  auto number = readNumber("page");
  writeln("The number is: ", number);

  auto bchoice = readChoice("continue", "abort");
  writeln("You chose to: ", bchoice ? "continue" : "abort");

  auto choices = ["Eat Pizza", "Sleep", "Watch 'Game of Thrones'", "Play Skyrim"];
  auto uichoice = readChoice(choices, "do nothing");
  if(uichoice == 0)
    writeln("You chose to do nothing.");
  else
    writeln("You chose to ", choices[uichoice-1]);

  printList("What do you feel like doing?", choices);
  uichoice = readUint();
  writeln("You chose option #", uichoice);
}

Now you can cd to $MOVIEMAN, type dub, and follow the instructions. In the next chapter, we'll work further on the UI.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.173.53