Chapter 22: a notes-keeper with options, built

Chapter 22's Notes keeps lines of text in a file, and takes its wishes from the command line: -a adds a note, -f names the file, -v says what it is doing. The program itself stays thin - it works out what was asked for, then two small functions do the work: readNotes and writeNotes. The IDE makes each shell; you type the logic, then run it with an option. To play it in the IDE: Help > Lessons > Getting Started with EK9…, or ek9 -ide -lesson book-notes.

The EK9 IDE as the lesson opens

What you'll build

The lesson builds this program from a file holding only its module line - the finished file, as the book prints it.

#!ek9
defines module book.notes

  defines function

    //Read every line of the file back into a list.
    readNotes()
      -> file as TextFile
      <- rtn as List of String: List() of String
      try
        -> input <- file.input()
        while input.hasNext()
          line <- input.next()
          if line?
            rtn += line

    //Write the whole list out. Opening a file for output starts it afresh,
    //so we always write every note, not just the new one.
    writeNotes()
      ->
        file as TextFile
        notes as List of String

      try
        -> output <- file.output()
        for note in notes
          output.println(note)

  defines program

    //The entry point stays thin: work out what was asked for, then let the functions do the work.
    Notes()
      -> argv as List of String

      stdout <- Stdout()
      stderr <- Stderr()

      usage <- "Usage: Notes [-v] [-f file] [-a note]"
      supported <- {
        "-v": String(),
        "-f": ":",
        "-a": ":"
      }
      options <- GetOpt(":", supported, usage).options(argv)

      verbose <- options contains "-v"
      filename <- options.getOrDefault("-f", "notes.txt")
      path <- FileSystemPath().inCurrentWorkingDirectory(filename)

      //Create the file only if it is not there yet; an existing one is left alone.
      //createFileIfAbsent() hands back a Result the compiler will not let us ignore.
      outcome <- path.createFileIfAbsent()
      if outcome.isError()
        stderr.println(`cannot create ${filename}: ${outcome.error()}`)

      if path.exists()
        if verbose
          stderr.println(`using ${filename}`)

        file <- TextFile(path)
        notes <- readNotes(file)

        newNote <- options.getOrDefault("-a", String())
        if newNote?
          notes += newNote
          writeNotes(file, notes)

        stdout.println(`notes (${length notes}):`)
        for note in notes
          stdout.println(`  ${note}`)

Step 1: readNotes — the file into a list

Choose Code > New Construct > Function…

The EK9 IDE once this is done: Choose Code > New Construct > Function…

readNotes is the function's name. Add one parameter, file of type TextFile, and set Returns to List of String. Choose Preview.

The EK9 IDE just before choosing Preview: readNotes is the function's name

The preview shows readNotes about to be written, file in and List of String out. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows readNotes about to be written, file in and List of String out

Select the readNotes() line, and above it type the blank line and the comment the book gives it. Saves when typed.

The EK9 IDE once this is done: Select the readNotes() line, and above it type the blank line and the comment the book gives it

Select the stub, from <- rtn as List of String? down to its //AI line, and type the work: a list to return, and a try that reads each line into it. Saves when typed.

The EK9 IDE once this is done: Select the stub, from <- rtn as List of String? down to its //AI line, and type the work

Step 2: writeNotes — the list into the file

Choose Code > New Construct > Function…

The EK9 IDE once this is done: Choose Code > New Construct > Function…

writeNotes is the function's name. Add two parameters, file of type TextFile and notes of type List of String, and leave Returns blank. Choose Preview.

The EK9 IDE just before choosing Preview: writeNotes is the function's name

The preview shows writeNotes about to be written, with both parameters. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows writeNotes about to be written, with both parameters

Select the writeNotes() line, and above it type the comment the book gives it - why every note is written, not just the new one. Saves when typed.

The EK9 IDE once this is done: Select the writeNotes() line, and above it type the comment the book gives it - why every note is written, not just the new one

Select the stub, from require file? down to its //AI line, and type the work: a try that writes each note on its own line. Saves when typed.

The EK9 IDE once this is done: Select the stub, from require file? down to its //AI line, and type the work

Step 3: The thin entry point

Choose Code > New Construct > Program…

The EK9 IDE once this is done: Choose Code > New Construct > Program…

Notes is the program's name. Add one parameter, argv of type List of String - the command line. Choose Preview.

The EK9 IDE just before choosing Preview: Notes is the program's name

The preview shows Notes about to be written, with argv. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows Notes about to be written, with argv

Select the Notes() line, and above it type the blank line and the comment the book opens the program with. Saves when typed.

The EK9 IDE once this is done: Select the Notes() line, and above it type the blank line and the comment the book opens the program with

Select the stub, from require argv? down to its //AI line, and type the program: the options, the file, reading the notes, adding one if asked, and listing them. Saves when typed.

The EK9 IDE once this is done: Select the stub, from require argv? down to its //AI line, and type the program

Step 4: Run it with an option

Choose Run > Run Program…

The EK9 IDE once this is done: Choose Run > Run Program…

Type -a milk into Arguments (space-separated), then choose Run.

The EK9 IDE just before choosing Run: Type -a milk into Arguments (space-separated), then choose Run

notes (1): and milk - the file was created, read back empty, the new note added and written, then listed. Run it again and the first note is read back too. Choose Close.

The EK9 IDE just before choosing Close: notes (1)

The result

Notes.ek9 now reads exactly as chapter 22 of Getting Started with EK9 shows it: two small functions that read and write the file, and a thin program that works out what was asked for from -v, -f and -a and lets them do the work. New Construct made each shell - the program's argv parameter in the same dialog - and you typed the logic.

The EK9 IDE when the lesson has finished

Where this lesson comes from