mirror of
https://github.com/Instadapp/Swap-Aggregator-Subgraph.git
synced 2024-07-29 21:57:12 +00:00
368 lines
12 KiB
Markdown
368 lines
12 KiB
Markdown
# Making a Movie CLI
|
||
|
||
_You can see the latest version of the result of this tutorial on Github at [https://github.com/infinitered/tutorial-movie](https://github.com/infinitered/tutorial-movie)!_
|
||
|
||
In this tutorial, we'll make a Gluegun-powered command-line interface called `movie`. Before doing this tutorial, make sure you've followed the installation instructions in [Getting Started](/getting-started). We will also be using [yarn](https://yarnpkg.com/) in this tutorial rather than `npm`. You can use `npm` if you want.
|
||
|
||
## Generate a new CLI
|
||
|
||
At your terminal prompt, run the following commands.
|
||
|
||
```
|
||
$ gluegun new movie --typescript
|
||
|
||
Generated movie CLI.
|
||
|
||
Next:
|
||
$ cd movie
|
||
$ yarn link
|
||
$ movie
|
||
|
||
$ cd movie
|
||
$ yarn link
|
||
$ movie --help
|
||
|
||
movie version 0.0.1
|
||
|
||
version (v) -
|
||
help (h) -
|
||
generate (g) -
|
||
movie -
|
||
```
|
||
|
||
At this point, open the folder in your editor. You should see something like this:
|
||
|
||

|
||
|
||
## Connect to IMDB's API
|
||
|
||
We want to hook into IMDB's API to find information about movies and actors. Luckily, there's a nice little [NPM package](https://github.com/worr/node-imdb-api) for that!
|
||
|
||
```
|
||
$ yarn add imdb-api
|
||
```
|
||
|
||
In order to use the API, you'll need an API key. We are not going to hard-code our API key into the CLI source. Instead, we'll ask the user for an API key and then store it locally.
|
||
|
||
_You can get a free API key (with a 1,000/day limit) [here from OMDB](http://www.omdbapi.com/apikey.aspx?__EVENTTARGET=freeAcct). Don't forget to click the link in the email to activate it!_
|
||
|
||
## Create an extension
|
||
|
||
In your CLI's `src/extensions` folder, make a new file, called `imdb-extension.ts`. Here's the source:
|
||
|
||
```typescript
|
||
// in src/extensions/imdb-extension.ts
|
||
import { GluegunToolbox } from 'gluegun'
|
||
import * as imdb from 'imdb-api'
|
||
|
||
module.exports = (toolbox: GluegunToolbox) => {
|
||
// grab the prompt tool from our toolbox
|
||
const { prompt } = toolbox
|
||
|
||
// get a movie
|
||
async function getMovie(name: string): Promise<imdb.Movie | null> {
|
||
const result = await prompt.ask({
|
||
type: 'input',
|
||
name: 'key',
|
||
message: 'API Key>',
|
||
})
|
||
|
||
if (result.key) return imdb.get({ name }, { apiKey: result.key, timeout: 30000 })
|
||
}
|
||
|
||
// attach our tools to the toolbox
|
||
toolbox.imdb = { getMovie }
|
||
}
|
||
```
|
||
|
||
The source is commented, so read through it. We define the `getMovie` function in the extension and then attach a new object to the toolbox that will contain the function (and any future ones we make). This will be available to any commands we make.
|
||
|
||
## Create a command
|
||
|
||
Now that we have the tools, let's build a command. We want to be able to search for a movie and display information about it, something like this:
|
||
|
||
```
|
||
$ movie search "The Road"
|
||
```
|
||
|
||
If they don't provide the movie title, we'll prompt them for it. We'll also prompt them for the API key.
|
||
|
||
Create a file `src/commands/search.ts` and put the following contents into it:
|
||
|
||
```typescript
|
||
import { GluegunToolbox } from 'gluegun'
|
||
|
||
module.exports = {
|
||
name: 'search',
|
||
alias: ['s'],
|
||
description: 'Searches for and displays information about a movie',
|
||
run: async (toolbox: GluegunToolbox) => {
|
||
// retrieve the tools from the toolbox that we will need
|
||
const { parameters, print, prompt, imdb } = toolbox
|
||
|
||
// check if there's a name provided on the command line first
|
||
let name = parameters.first
|
||
|
||
// if not, let's prompt the user for one and then assign that to `name`
|
||
if (!name) {
|
||
const result = await prompt.ask({
|
||
type: 'input',
|
||
name: 'name',
|
||
message: 'What movie?',
|
||
})
|
||
if (result && result.name) name = result.name
|
||
}
|
||
|
||
// if they didn't provide one, we error out
|
||
if (!name) {
|
||
print.error('No movie name specified!')
|
||
return
|
||
}
|
||
|
||
// now retrieve the info from IMDB
|
||
const movie = await imdb.getMovie(name)
|
||
if (!movie) {
|
||
print.error(`Couldn't find that movie, sorry!`)
|
||
return
|
||
}
|
||
|
||
// success! We have movie info. Print it out on the screen
|
||
print.debug(movie)
|
||
},
|
||
}
|
||
```
|
||
|
||
The command is well commented, so read through those to see what we're doing. Notice that we're using the `imdb.getMovie` function that we created in the `imdb-extension.ts` extension prior to this.
|
||
|
||
Run it on the command line and search for a movie. You'll notice that the extension asks for an API key -- enter the one you got from OMDB there.
|
||
|
||
```
|
||
❯ movie search ✱
|
||
? What movie? The Room
|
||
API KEY> <ENTER YOUR API KEY HERE>
|
||
```
|
||
|
||
If you did it right, you should get something like the following output:
|
||
|
||
```js
|
||
vvv -----[ DEBUG ]----- vvv
|
||
Movie {
|
||
title: 'The Room',
|
||
_year_data: '2003',
|
||
year: 2003,
|
||
rated: 'R',
|
||
released: 2004-03-03T08:00:00.000Z,
|
||
runtime: '99 min',
|
||
genres: 'Drama',
|
||
director: 'Tommy Wiseau',
|
||
writer: 'Tommy Wiseau',
|
||
actors: 'Tommy Wiseau, Greg Sestero, Juliette Danielle, Philip Haldiman',
|
||
plot: 'In San Francisco, we follow Johnny, a man who has a girlfriend, Lisa, and also his best friend, Mark. Lisa has been cheating on Johnny with Mark and Johnny doesn\'t know! Will Johnny ever find out? Will Mark still be Johnny\'s best friend?',
|
||
languages: 'English',
|
||
country: 'USA',
|
||
awards: '1 win.',
|
||
poster: 'https://images-na.ssl-images-amazon.com/images/M/MV5BMTg4MTU1MzgwOV5BMl5BanBnXkFtZTcwNjM1MTAwMQ@@._V1_SX300.jpg',
|
||
ratings:
|
||
[ { Source: 'Internet Movie Database', Value: '3.6/10' },
|
||
{ Source: 'Rotten Tomatoes', Value: '26%' },
|
||
{ Source: 'Metacritic', Value: '9/100' } ],
|
||
metascore: '9',
|
||
rating: '3.6',
|
||
votes: '56,264',
|
||
imdbid: 'tt0368226',
|
||
type: 'movie',
|
||
dvd: '17 Dec 2005',
|
||
boxoffice: 'N/A',
|
||
production: 'Chloe Productions',
|
||
website: 'http://theroommovie.com/',
|
||
response: 'True',
|
||
series: false,
|
||
imdburl: 'https://www.imdb.com/title/tt0368226' }
|
||
^^^ -----[ DEBUG ]----- ^^^
|
||
```
|
||
|
||
Success!
|
||
|
||
## Save the API key
|
||
|
||
We don't want to ask the user for an API key every time. So, if it's a valid API key, we want to save it to the user's filesystem and retrieve it on every load.
|
||
|
||
We also don't want to be asking for user input in our _extension_. That's the role of the _command_. (Read more about this in [Guide: Architecting Your Gluegun CLI](/guide-architecture).)
|
||
|
||
Let's go back to our IMDB extension and add a few tools. Here's the result:
|
||
|
||
```typescript
|
||
import { GluegunToolbox } from 'gluegun'
|
||
import * as imdb from 'imdb-api'
|
||
|
||
module.exports = (toolbox: GluegunToolbox) => {
|
||
const { filesystem } = toolbox
|
||
|
||
// location of the movie config file
|
||
const MOVIE_CONFIG = `${filesystem.homedir()}/.movie`
|
||
|
||
// memoize the API key once we retrieve it
|
||
let imdbKey: string | false = false
|
||
|
||
// get the API key
|
||
async function getApiKey(): Promise<string | false> {
|
||
// if we've already retrieved it, return that
|
||
if (imdbKey) return imdbKey
|
||
|
||
// get it from the config file?
|
||
imdbKey = await readApiKey()
|
||
|
||
// return the key
|
||
return imdbKey
|
||
}
|
||
|
||
// read an existing API key from the `MOVIE_CONFIG` file, defined above
|
||
async function readApiKey(): Promise<string | false> {
|
||
return filesystem.exists(MOVIE_CONFIG) && filesystem.readAsync(MOVIE_CONFIG)
|
||
}
|
||
|
||
// save a new API key to the `MOVIE_CONFIG` file
|
||
async function saveApiKey(key): Promise<void> {
|
||
return filesystem.writeAsync(MOVIE_CONFIG, key)
|
||
}
|
||
|
||
// get a movie
|
||
async function getMovie(name: string): Promise<imdb.Movie | null> {
|
||
const key = await getApiKey()
|
||
if (key) return imdb.get({ name }, { apiKey: key, timeout: 30000 })
|
||
}
|
||
|
||
// attach our tools to the toolbox
|
||
toolbox.imdb = { getApiKey, saveApiKey, getMovie }
|
||
}
|
||
```
|
||
|
||
We've added a few functions as well as the `MOVIE_CONFIG` file. We're using the `filesystem` tool from Gluegun.
|
||
|
||
Also update your command to ask for the API key:
|
||
|
||
```typescript
|
||
import { GluegunToolbox } from 'gluegun'
|
||
const API_MESSAGE = `
|
||
Before using the movie CLI, you'll need an API key from OMDB.
|
||
Go here: http://www.omdbapi.com/apikey.aspx?__EVENTTARGET=freeAcct
|
||
Once you have your API key, enter it below.
|
||
API KEY>`
|
||
|
||
module.exports = {
|
||
name: 'search',
|
||
alias: ['s'],
|
||
description: 'Searches for and displays information about a movie',
|
||
run: async (toolbox: GluegunToolbox) => {
|
||
// retrieve the tools from the toolbox that we will need
|
||
const { parameters, print, prompt, imdb } = toolbox
|
||
|
||
// check if there's a name provided on the command line first
|
||
let name = parameters.first
|
||
|
||
// if not, let's prompt the user for one and then assign that to `name`
|
||
if (!name) {
|
||
const result = await prompt.ask({
|
||
type: 'input',
|
||
name: 'name',
|
||
message: 'What movie?',
|
||
})
|
||
if (result && result.name) name = result.name
|
||
}
|
||
|
||
// if they didn't provide one, we error out
|
||
if (!name) {
|
||
print.error('No movie name specified!')
|
||
return
|
||
}
|
||
|
||
// check if we have an IMDB API key
|
||
if ((await imdb.getApiKey()) === false) {
|
||
// didn't find an API key. let's ask the user for one
|
||
const result = await prompt.ask({
|
||
type: 'input',
|
||
name: 'key',
|
||
message: API_MESSAGE,
|
||
})
|
||
|
||
// if we received one, save it
|
||
if (result && result.key) {
|
||
imdb.saveApiKey(result.key)
|
||
} else {
|
||
// no API key, exit
|
||
return
|
||
}
|
||
}
|
||
|
||
// now retrieve the info from IMDB
|
||
const movie = await imdb.getMovie(name)
|
||
if (!movie) {
|
||
print.error(`Couldn't find that movie, sorry!`)
|
||
return
|
||
}
|
||
|
||
// success! We have movie info. Print it out on the screen
|
||
print.debug(movie)
|
||
},
|
||
}
|
||
```
|
||
|
||
Running it again will ask for our API key. Subsequent runs won't ask for it anymore, and you should see a `.movie` file in your home folder that contains your API key.
|
||
|
||
## Add another command
|
||
|
||
We want to be able to reset the API key if we need to. Let's create another command for `movie api reset`, located at `src/commands/api/reset.ts`. Add this code:
|
||
|
||
```typescript
|
||
import { GluegunToolbox } from 'gluegun'
|
||
const CONFIRM_MESSAGE = 'Are you sure you want to reset the IMDB API key?'
|
||
|
||
module.exports = {
|
||
name: 'reset',
|
||
run: async (toolbox: GluegunToolbox) => {
|
||
// retrieve the tools from the toolbox that we will need
|
||
const { prompt, print, imdb } = toolbox
|
||
|
||
// confirmation, because this is destructive
|
||
if (await prompt.confirm(CONFIRM_MESSAGE)) {
|
||
// delete the API key
|
||
await imdb.resetApiKey()
|
||
print.info('Successfully deleted IMDB API key.')
|
||
}
|
||
},
|
||
}
|
||
```
|
||
|
||
We also need to update the imdb extension:
|
||
|
||
```typescript
|
||
// src/extensions/imdb-extension.ts
|
||
|
||
// omitted a lot of code ...
|
||
|
||
// reset the API key
|
||
async function resetApiKey(): Promise<void> {
|
||
await filesystem.removeAsync(MOVIE_CONFIG)
|
||
}
|
||
|
||
// attach our tools to the toolbox
|
||
toolbox.imdb = { getApiKey, saveApiKey, getMovie, resetApiKey }
|
||
}
|
||
```
|
||
|
||
At the command line, run `movie api reset` and you should see the prompt to delete the key.
|
||
|
||
## Additional Exercises
|
||
|
||
As additional exercises, try doing these things with your new CLI:
|
||
|
||
1. Show a customized help screen with `movie help`. (Hint: add a `src/commands/help.ts` and remove the `.help()` in `src/cli.ts`)
|
||
2. Show nicer output from the `search.js` command. (Hint: replace `print.debug(movie)` with a table using `print.table()`)
|
||
3. Add the ability to search for actors, not just movie titles
|
||
|
||
## Notes
|
||
|
||
- The architecture of the above CLI works, but as it grows, you'll want to start organizing it a little better. Read [Guide: Architecting Your Gluegun CLI](/guide-architecture) to learn more.
|
||
|
||
_Questions? Jump in our [Infinite Red Community Slack](http://community.infinite.red) in the #gluegun channel and ask away!_
|