- Introduce CLI, or Command Line Interface, programs.
- Running CLI applications.
- Use
gets
for user input. - Introduce stubbing in tests.
- Integrate procedural Ruby basics to create a simple CLI Jukebox.
- Advanced: play music from a Ruby program!
A CLI, or command line interface, allows a user to interface, or interact with, a computer's operating system or a particular application. You've already become comfortable interacting with the command line to navigate files, connect with Github and test your programs. In a command line application, the user will respond to a prompt that your program will output to the terminal. The user's response, or input, will be received by the application and the application will then carry out the programmed response based on that input.
For example, I might have a command line application which, once run, will ask the user for their location and in return ouput the current weather for that location to the terminal.
In order to run our program from the command line and allow our user to interact with our program as described above, we need to set up a few things.
First, your program needs a bin
directory. "Bin" is short for "binary" and is just another way to refer to executable files. Accordingly, your executable files belong in this directory.
Executable files are any files that contain instructions in a form that a computer's operating system or application can understand and follow. Any executable files we place in our bin directory need to begin with the following line:
#!/usr/bin/env ruby
This is often referred to as a "shebang line" and it tells the shell which interpreter to use to execute the remainder of the file.
Using the above setup, you can run your program by typing ruby bin/< your file name >
into the command line.
Alternatively, you can execute your program by simply typing bin/< your file name >
into the command line, since the shebang line at the top of your executable file is already telling the shell to use Ruby to interpret the rest of the file.
You're going to write a jukebox that introduces itself to the user and then asks for the user's input on the command line.
There are a number of ways to accept user input. Today, we'll be building a Jukebox that functions primarily by accepting user input via the command line.
To demonstrate the example of user input via the command line, let's make a small program inside our jukebox.rb
file. Our program will ask a user for their name, collect the user input via the gets
command, and then say hello to that user. Let's follow the code block below:
def say_hello(name)
"Hi #{name}!"
end
puts "Enter your name:"
users_name = gets.chomp
puts say_hello(users_name)
When we run ruby lib/jukebox.rb
, it's going to print out Enter your name:
, then the Terminal prompt will return, and you'll type in your name. The user input is obtained by the gets
method, then any trailing whitespace gets removed via the chomp
method, and finally the result is stored in the users_name
local variable (let's assume users_name = "Avi Flombaum"
). Finally, the program will print out the output of the say_hello
method, which is "Hi, Avi Flombaum!"
.
Now, go head and delete or comment out the code we just wrote. That was just for practice (and fun). Let's move on to the Jukebox.
Your jukebox should respond to 4 commands: help
, play
, list
, and exit
.
When you run the program in the command line it should greet the user and prompt them for input. Your jukebox should respond to 4 commands: help
, play
, list
, and exit
.
- The help command should output instructions for the user on how to use the jukebox.
- The list command should output a list of songs that the user can play.
- The play command should ask a user to input a song name or number. It should then output 'Playing Phoenix - 1901' or whatever the song name is.
- If the user types in exit, the jukebox should say goodbye and the program should shut down.
Let's take a closer look at the methods we'll need to build in order to get our Jukebox up and running as described here.
We'll be building a series of methods that enact the desired behavior of our Jukebox. In lib/jukebox.rb
we'll create a help
, play
, list
and exit_jukebox
method. Then, we'll build a run
method that calls on all of these "helper" methods to implement the behavior of our program. The executable file in bin/jukebox
file will simply call the run
method that we define in lib/jukebox.rb
.
Open up lib/jukebox.rb
and you should see this:
songs = [
"Phoenix - 1901",
"Tokyo Police Club - Wait Up",
"Sufjan Stevens - Too Much",
"The Naked and the Famous - Young Blood",
"(Far From) Home - Tiga",
"The Cults - Abducted",
"Phoenix - Consolation Prizes",
"Harry Chapin - Cats in the Cradle",
"Amos Lee - Keep It Loose, Keep It Tight"
]
This is the list of songs that our jukebox will be working with. The methods we will write will operate on this arrays of songs.
This method should puts
out the following:
I accept the following commands:
- help : displays this help message
- list : displays a list of songs you can play
- play : lets you choose a song to play
- exit : exits this program
This method takes in an argument of the songs
array of puts
out the following:
1. Phoenix - 1901
2. Tokyo Police Club - Wait Up
3. Sufjan Stevens - Too Much
4. The Naked and the Famous - Young Blood
5. (Far From) Home - Tiga
6. The Cults - Abducted
7. Phoenix - Consolation Prizes
8. Harry Chapin - Cats in the Cradle
9. Amos Lee - Keep It Loose, Keep It Tight
Hint: Use the Array#each_with_index
method to access the index number of a given element as you step through the iteration. Remember that arrays are indexed starting at 0
. So, adding 1
to the index number at a given step of the iteration will result in the correct song number for the purposes of outputting your list of songs.
This method also takes in an argument of the songs
array. It first puts
out the prompt: "Please enter a song name or number:"
. It then stores the user's response using gets.chomp
.
If the user's response is a valid song number or song name, the method should puts
out: "Playing <song name>"
. Otherwise, it should puts
out: "Invalid input, please try again"
.
This method is simple. It puts
out: "Goodbye"
.
This method is a little trickier. It will use the other methods we built, our "helper" methods, to actually enact the running of our Jukebox.
First, this method should call on the help
method to show the user the available commands. Then, it should puts
out the prompt: "Please enter a command:"
. It should capture the user's response using gets.chomp
or gets.strip
.
We need to keep our program running as long as the user's input is not "exit"
. Use a loop to continue asking the user for input until or unless their input is "exit"
. Use if
or case
statements to determine how your program will respond to a user's input. For example, if their input is "list"
, call the list
method, if their input is "play"
, call the play
method, if their input is "help"
, call the help
method and if their input is "exit"
, call the exit_jukebox
method and break out of your loop to stop the program.
Packaged with this repository is a runner file bin/jukebox
.
This executable file, bin/jukebox
, is a Ruby file, but the ".rb" extension has been left off. The ".rb" extension is not mandatory for Ruby files, it is just a nice thing to have for the purposes of identifying the type of the file. However, it is a convention to leave the file extension off of executable files, which comes from true binary files that contain pure
machine code (1's and 0's) rather than human readable source code like Ruby.
Open up bin/jukebox
and you will see the following:
#!/usr/bin/env ruby
require_relative "../lib/jukebox"
songs = [
"Phoenix - 1901",
"Tokyo Police Club - Wait Up",
"Sufjan Stevens - Too Much",
"The Naked and the Famous - Young Blood",
"(Far From) Home - Tiga",
"The Cults - Abducted",
"Phoenix - Consolation Prizes",
"Harry Chapin - Cats in the Cradle",
"Amos Lee - Keep It Loose, Keep It Tight"
]
run(songs)
First, we require the lib
file in which we defined our code. Now this executable file has access to all of the methods we defined in lib/jukebox.rb
. Then we have our songs
array. Then, we call run(songs)
, the method we defined to enact the behavior of our program.
Assuming you are in the root directory of the jukebox project, run your jukebox from the command line with ruby bin jukebox
or simply bin/jukebox
(thanks to our shebang line).
You already know that your Jukebox command line application relies on a user's input to run. In order to test our application programmatically––in other words, test it without having to run and interact with the program ourselves––we will rely on stubbing in our test suite.
Stubbing refers to the fake implementation of a method. In the context of this test suite, we will stub the gets
method call that our program relies on to obtain user input and feed it back into the application. In this way, we can simulate the CLI and effectively test our app.
Let's compare the regular running of our Jukebox––i.e., when a real live human interacts with it––with the testing of our Jukebox. We'll do this by taking a closer look at the play
method.
Without giving away all of the implementation of this method and spoiling your fun, we can imagine that our play
method might ask the user what song they want to hear, store that input using the gets
method and use it to determine what song to play.
Remember, the gets
method lets the user input a line and returns that line to your program. In our test suite, we need to fake this interaction and we'll use stubbing to do it. Check out the following line from our jukebox_spec.rb
:
it "can use a full song name to find a song" do
allow(self).to receive(:gets).and_return("Phoenix - 1901")
expect{ play(songs) }.to output(/Phoenix - 1901/).to_stdout
end
Let's break down what's happening above:
With allow(self).to receive(:gets).and_return("Phoenix - 1901")
,
we are faking a call to the gets
method and telling it to return a value of "Phoenix - 1901"
.
Now, when we call the play
method on the following line, it has a gets
value to operate on.
Then, we test the output of the play
method with
expect{ play(songs) }.to output(/Phoenix - 1901/).to_stdout
This line uses one of Learn's output matchers.
Learn's output matchers provide a way to check that your program has emitted output to $stdout
––a global variable that keeps track of the current standard output to your terminal. Don't worry too much about global variables right now, just know that they can be accessed from anywhere in your program.
The .to_stdout
method works by temporarily replacing the $stdout
variable. When the output
method is used with a string or a regex (as above), your test will pass if the block outputs a string to_stdout
that matches the given string or regex.
So, these lines of our test suite:
allow(self).to receive(:gets).and_return("Phoenix - 1901")
expect{ play(songs) }.to output(/Phoenix - 1901/).to_stdout
Accomplish the following:
- Establish a
gets
value for theplay
method to utilize - Execute the
play
method contained in the block - Check to see if the
play
method outputs content that matches the regular expression/Phoenix - 1901/
In lib/advanced_jukebox.rb
you'll be building on your jukebox so that it allows the user to play songs from the command line. You'll be playing the songs in audio/Emerald-Park
, which contains some mp3 files.
In lib/advanced_jukebox.rb
you'll see a my_songs
hash which has keys of song names and values of the path to of each mp3 file (in jukebox-cli/audio/Emerald-Park/<file name>.mp3
).
Change the value of each hash key to reflect the correct file path. For example, if this jukebox-cli directory is in Users/<your name>/Desktop/Dev/
, the value of the first key should be Users/<your name>/Desktop/Dev/jukebox-cli/audio/Emerald-Park/01.mp3
. If you're not sure what the path to the file is, cd into the directory of this lab in your terminal and type pwd
. This stands for "print working directory" and will return the path to your current location.
It is possible to play audio files via the command line by simply typing open <path to audio file>
. This will launch your computer's audio player (iTunes, for example). In order to tell Ruby to execute a command line command, we use the following syntax:
system 'open <path to audio file'
This is the code that you'll be adding to your play
method in order to play the audio files we've provided for you.
Check out the code in advanced_jukebox.rb
, we've left the method definitions for you and provided you the song hash, my_songs
.
Follow the instructions in the comments of advanced_jukebox.rb
to get your
jukebox working. This is a bonus challenge, so there are no tests. Just get your jukebox playing music.
Hint: You can open a file from the command line by typing open <filename>
in your terminal. (If you are not using a Mac, the command you type will be a little different. If the file is an audio file, you computer will open the file and play it. We want our Ruby Jukebox program to execute that command for us. Ruby has method, system
, that is called with an argument of a string that contains the command line command you want to execute. When Ruby invokes that method, the command you placed in the string will be executed. So, to play a song from a Ruby file, you can use system "open #{filename}"
.
Run your advanced jukebox program by typing ruby bin/advanced_jukebox
in your terminal. This is the runner file for the advanced_jukebox.rb
code.
View Build a Jukebox on Learn.co and start learning to code for free.