Motivation

Comic: CHRIS HADFIELD: An astronaut’s advice— layout: post title: “Command Line Applications” description: “" toc: true tags:

  • Python
  • File I/O
  • Shell Scripting

College passes by really fast, and if you don’t stop to document it, you will forget all the memories and things you are grateful for! So let’s use the command line, a tool you’ve been introduced to in your CS classes, to track our gratitude.

print("Hello gratitude journal")

Hosted by: Drshika Asher

Getting Ready

Local Machine Instructions

  1. Learn a bit of git on the command line. You can use any intro tutorial, but this web browser one looks good.

  2. Learn some basics of how a command line works. Here is a quick crash course with links to resources to learn even more. The command line is intimidating, but you’ll wonder how you lived without it once you get used to it. It’s part of almost every programmer’s toolbox.

Extras:

  1. Learn a bit of git on the command line. You can use any intro tutorial, but this web browser one looks good.

  2. Learn some basics of how a command line works. Here is a quick crash course with links to resources to learn even more. The command line is intimidating, but you’ll wonder how you lived without it once you get used to it. It’s part of almost every programmer’s toolbox.

Workshop

We will build a Command-Line app (CLI) that helps you be grateful every day in this course. In addition, you will learn how to work with different Python Libraries, reading and writing from text and YAML files, and taking user input.

Prior Knowledge: Intermediate/Advanced Coding
Note: I’ve done this on macOS Monterey 12.1. You should be able to do this on any computer. Your progress shouldn’t be drastically different, but you may need to adjust depending on your OS. I used python 3.9.9.

Resources

Background

Libraries

Create a file called requirements.txt and fill it with this. This is a standard convention in Python projects for listing all the libraries required.

questionary
datetime
pyyaml
pyfiglet
plumbum
ruamel.yaml

Once you’ve saved this file, run the following to install them (if you use python3 to execute commands, then use pip3):

$ pip install -r requirements.txt
[install output]
Note: for the code fences for terminal commands:
$ (this part is what you type)
(this part is the output)

The -r flag tells pip to look at requirements.txt and install everything in it.

Let me explain how we’re using each library.

  • questionary give us fancy interactive menu interfaces.
  • pyfiglet provides ASCII art displays.
  • plumbum gives us a way of accepting input, displaying help information, and calling existing system commands.
  • pyyaml allows us to set a file path and your name
  • datetime gives us the date and time
  • ruaeml.yaml extends the capabilities of pyyaml and allows us to format strings properly

What the code does

Logical Level

Let’s imagine this gratitude journal in real life. Here are some steps you may take:

  1. Get a Journal
  2. Open the Journal to the right page (for today)
  3. Journal
  4. Close the Journal
  5. View past Journals

Implementation

Now, here is the translation of those basic journal functionalities into code.

Main Journal functions:

  1. open_journal
    • Help the user get a journal with their name and store it in their location
  2. add_page
    • Open the right entry and allow the user to write what they are grateful for
  3. add_content
    • Give the user a prompt to journal and collect their thoughts
  4. read_entries
    • Let the user “flip” through their digital journal entries

The Journal (GJournal) Class

Create a file called journal.py. We’ll use cli from the plumbum library to create the base template for our command-line application.

from plumbum import cli 

class GJournal(cli.Application):
    def main(self):
        print("Welcome to Fancy Gratitude Journal!")
if __name__ == "__main__":
    GJournal() 

When we run this using python journal.py, we get the following output:

$ python journal.py
Welcome to Fancy Gratitude Journal!

We add the name == “main” condition so that we can choose to run our program in different ways. For example, when running the program normally through Python, the condition will be true and the code inside will run, but in other cases, it won’t.

For example, if we want to write tests for our code (not covered in this tutorial but check out testing101), we would need to run the program a differently

Provide help

Since you cannot see the full range of commands you can use in a CLI with neat little buttons, most CLI interfaces come with the --help or -h command. This command provides you with the full range of flags or switches you can run a program for different functionality.

Let’s see what we get out of the box for Plumbum.

$ python journal.py --help
Usage:
    journal.py [SWITCHES]

Meta-switches:
    -h, --help         Prints this help message and quits
    --help-all         Prints help messages of all sub-commands and quits
    -v, --version      Prints the program's version and quits

Not a bad start. Now we should give our program a version number. In the real world, remember to change the version number as you add new changes so that people know when to update.

from plumbum import cli 

class GJournal(cli.Application):
    VERSION = "0.0"

    def main(self):
        print("Hello World")
if __name__ == "__main__":
    GJournal()

This gets our version number display working nicely:

$ python journal.py --version
journal.py 1.3
$ python journal.py -h
journal.py 1.3

Usage:
    journal.py [SWITCHES]

Meta-switches:
    -h, --help         Prints this help message and quits
    --help-all         Prints help messages of all sub-commands and quits
    -v, --version      Prints the program's version and quits

Pretty Graphics

We’ll use Figlet from the pyfiglet library to make a fancy greeting for the user. I also imported colors from plumbum to get custom colors for the slanted font.

from pyfiglet import Figlet
from plumbum import colors, cli 

def print_banner(text):
    with colors['LIGHT_SEA_GREEN']:
        print(Figlet(font='slant').renderText(text))

class GJournal(cli.Application):
    VERSION = "0.0"

    def main(self):
        print_banner("Gratitude Journal")

if __name__ == "__main__":
    GJournal()

Let’s abstract this away into another function. In general, it’s good practice to separate different functionality into separate helper functions so that you can test and debug easily. For example, the main function doesn’t care about how the fancy greeting is made, just that it is printed and shown to the user.

Saving the Journal

Now create another file called config.yaml. We will use this to store information about where your journal is persistent across different program runs.

When you run the program, python collects your inputs and stores them in variables. If you don’t save the data to an external file, that data will be lost, and the next time you run your program, it will go away. So we will use this file to store our configuration.

author: ""
journal_name: ""

Here’s what you should put for now.

Creating the Journal

Saving Configurations

Since the information about our journal is encoded on this YAML file, we will need to create helper functions to load and unload from it. First let’s create the save_config() helper function. It will take two parameters: filename and config. config will store the author and the folder name in the YAML format. filename is the YAML file you want to save config to.

import yaml, ruamel.yaml

def save_config(filename, config):
    yaml = ruamel.yaml.YAML()

    with open(filename, "w") as file:
            yaml.dump(config, file)

We are also using a library called ruamel.yaml because it allows us to save the “” on the values in our dictionary. Otherwise, we run into errors trying to change directories in later steps.

To open a file for reading or writing (in this case, we used the w for writing), you usually have to use an open('file path, 'w') paired with a file.close() statement and handle any exceptions. Using the with open(text file, 'w') as filename: syntax, we can make our code cleaner and handle any exceptions.

While we’re at it, let’s also write out the code to load our data from the YAML file. First, let’s create two global variables to store the author and journal_name where we will be saving our entries. We’re going to use the same syntax that we discussed earlier and assign our global variables to that of the YAML file.

import os

author = ""
journal_name = ""

def load_config(filename):
    global author, journal_name
    if not os.path.exists(filename):
        save_config(filename, {
            "author": '',
            "journal_name": ''
        })

    with open(filename, "r") as file:
        data = yaml.safe_load(file)
    author = data['author']
    journal_name = data['journal_name']

The first part checks if we already have a YAML file and creates it if we don’t. The second part opens the file for reading (r) and loads the data from the YAML file into a dictionary. Then we assign the dictionary values to the global variables.

Now add this after you print the banner in the main method of your GJournal class.

[...]
class GJournal(cli.Application):

    def main(self):
        global journal_name,author

        load_config("config.yaml")
        print_banner("Gratitude Journal")
    [...]

Create Journal Method

Before creating our create_journal method, let’s create a helper to make a folder. Now just in case we run into any errors while making the directory, we will add some print statements to check if it has been made. Wrap os.makedirs(path) in a try, except, else statement. We’re going to try to make the directory, but if it does not work, we will except an OSError and print out a message to the user using a fstring (read more about fstrings here). Once we make the folder, let’s change directories into that folder and add a page.

We can use cd <folder name> to change directories in the terminal. But in python, we can use os.chdir() to go into our folder of choice. Likewise, we use os.makedirs() as the pythonic equivalent of mkdir <folder name>.

def init_folder(folder_name):
    try:
        os.makedirs(folder_name)
    except OSError:
        print(f"Creating the directory {folder_name} has failed.")

    os.chdir(journal_name)
    add_page()

Now let’s create the function actually to make our journal. First, we need to get the author from the user to create a folder named journal_name and then save our configuration into the YAML file. Then we call our helper to make the folder and add a page. Here’s what it looks like in python:

import questionary
from questionary import prompt

def create_journal():
    global author, journal_name
    author = ruamel.yaml.scalarstring.DoubleQuotedScalarString(questionary.text("What is your name?").ask())
    
    journal_name = ruamel.yaml.scalarstring.DoubleQuotedScalarString(author + "-Journal")t

    my_dict = dict(author=author, journal_name=journal_name)

    save_config("config.yaml", my_dict)
    init_folder(journal_name)

I’m using ruamel.yaml.scalarstring.DoubleQuotedScalarString() as a wrapper to preserve double quotes.

Pretty Selection Menus

Now, what’s the point of writing all this functionality if the user cannot use it when they run the app? Let’s add a pretty selection menu with the help of questionary. Use the select().ask() method from questionary to ask the user to choose what they want to do.

choice = questionary.select(
    "What would you like to do",
    choices=[
        'Journal',
        'Read Entries',
        'Quit'
    ]).ask()

Now the user will be able to choose to either journal, read their pre-existing entries or quit.

If the user chooses to journal, there are two possibilities. They either already have a journal, or they don’t. If they don’t, we should create the journal first and then open it (since we cannot open a journal that does not exist!). If they do, we can just open the journal.

If the user chooses to quit, we can give them a nice message letting them know they are quitting the app.

If the user chooses to read entries, we will call the method that will take care of the functionality (which we will build in the last section).

if choice == 'Journal':
    if journal_name == "": 
        create_journal()
    open_journal()
    elif choice == 'Read Entries':
        read_entries()
    elif choice == 'Quit':
        pass

You can just create blank template methods with pass in the second line to allow your program to run. Or you can print something so that you know that you are in that method.

def open_journal():
    pass

Now try running it! You should get the cute banner to display and the options for choices.

$ python journal.py

Here’s everything you should have by this time:

from pyfiglet import Figlet
from plumbum import colors, cli 
import questionary
import yaml, ruamel.yaml

def print_banner(text):
    with colors['LIGHT_SEA_GREEN']:
        print(Figlet(font='slant').renderText(text))

author = ""
journal_name = ""

def load_config(filename):
    global author, journal_name
    if not os.path.exists(filename):
        save_config(filename, {
            "author": '',
            "journal_name": ''
        })

    with open(filename, "r") as file:
        data = yaml.safe_load(file)
    author = data['author']
    journal_name = data['journal_name']

# save the config to a file
def save_config(filename, config):
    yaml = ruamel.yaml.YAML()

    with open(filename, "w") as file:
            yaml.dump(config, file)

def init_folder(folder_name):
    #making the directory
    try:
        os.makedirs(folder_name)
    except OSError:
        print(f"Creating the directory {folder_name} has failed.")

    os.chdir(journal_name)
    add_page()

def create_journal():
    #prompt user to name journal
    global author, journal_name
    author = ruamel.yaml.scalarstring.DoubleQuotedScalarString(questionary.text("What is your name?").ask())
    
    #find the place to save the journal
    journal_name = ruamel.yaml.scalarstring.DoubleQuotedScalarString(author + "-Journal")

    my_dict = dict(author=author, journal_name=journal_name)

    save_config("config.yaml", my_dict)
    init_folder(journal_name)

def open_journal():
    pass

def read_entries():
    pass

def add_page():
    pass

class GJournal(cli.Application):
    VERSION = "0.0"

    def main(self):
        global journal_name,author,push

        load_config("config.yaml")
        print_banner("Gratitude Journal")
    
        choice = questionary.select(
        "What would you like to do",
        choices=[
            'Journal',
            'Read Entries',
            'Quit'
        ]).ask()
        if choice == 'Journal':
            if journal_name == "":
                create_journal()
            else:
                open_journal()
        elif choice == 'Read Entries':
            read_entries()
        elif choice == 'Quit':
            print("Goodbye, have a lovely day!")

if __name__ == "__main__":
    GJournal()

Opening the journal

Open Journal Method

Now let’s build out the logic for opening our journal. We will follow the naming convention that each entry is titled with today’s date in Year-month-day format. The contents of the file will be the journal entry.

To name the entry, we will use the datetime module from python format it as a string. We will add .txt to the end because each entry will be a text file.

from datetime import datetime
today_entry = str(datetime.today().strftime('%Y-%m-%d')) + ".txt"

However, if our entry already exists, we don’t want to overwrite the existing one! So, we will call another method to take care of that. First, let’s check our folder to see if there are already entries for today. We use the cd or change directory command to go into the correct folder in the terminal. Then we use the ls command to list all the directories available to us. Here’s the translation of this in python.

os.chdir(journal_name)
entry_list = os.listdir()

We have saved the names of the journals as a list. Now, we must loop through the journals to see if there’s an entry for today. We can append content to the page if there is one for today. If there is no content, we can create a page. We can create a page if the folder is empty (therefore, the list is empty). Here is what this logic looks like in python

import os, fnmatch
#looping through the list of journals
if not entry_list:
    add_page()
for entry in entry_list:
    if fnmatch.fnmatch(entry, today_entry):
        add_content(today_entry)
    else:
        add_page()

Add Page Method

Since adding a page is quite different from opening the journal, let’s separate that into a new function for adding a page. This function is super simple. First, follow our naming convention to create the file name. Next, use the open() function to create the text file (we will use the x file since we are using the function exclusively for file creation). Then, call the function to add content to the file.

def add_page():
    today_entry = str(datetime.today().strftime('%Y-%m-%d'))+ ".txt"
    open(today_entry, 'x')
    print(f"Created Entry: {today_entry}")
    add_content(today_entry)

Here’s everything you should have in this part:

def add_page():
    today_entry = str(datetime.today().strftime('%Y-%m-%d'))+ ".txt"
    open(today_entry, 'x')
    print("Created Entry" + today_entry)
    add_content(today_entry)

def add_content(title):
    pass

def open_journal(): 
    today_entry = str(datetime.today().strftime('%Y-%m-%d')) + ".txt"
    os.chdir(journal_name)
    journal_list = os.listdir()
    if not entry_list:
        add_page()
    for entry in entry_list:
        if fnmatch.fnmatch(entry, today_entry):
            add_content(today_entry)
        else:
            add_page()

(Actually) Journaling

Add Content Method

Now, for the most important part. Let’s build the functionality for writing an entry. Our function will take a parameter, which is a variable that we pass to the function. Using our with syntax from before, we will open the text file as a string stream for appending (which allows us to add without destroying the pre-existing content). Then, we will ask the user what they want to journal for today. After this, we will use the textwrap package to wrap the text nicely. Finally, we will write this nicely formatted text to the text file.

import textwrap 
def add_content(title):
    with open(title, 'a') as entry:
        writing = questionary.text("What are you grateful for?").ask()
        prettier_writing = textwrap.fill(writing) + "\n"
        entry.write(prettier_writing)

And that’s the entire function! You can also get creative here by adding multiple fun prompts or referring to the questionary documentation to track your mood for the day.

View Past Journal Entries

Read Entries Method

Okay, last part! You got this :) Let’s allow our users to read what they have written in the past. We want to navigate to the folder and list out all the files inside, allowing the user to choose which one to read. Then we can display the contents of the file.

Let’s start by navigating to the directory in which our entries are. Then save all the journals to a list. To create a custom question, we must create a dictionary following the syntax from the questionary. We will prompt the user with this question and save the response as the entry we are looking to print out. Then we will use the with syntax to open the file as a string stream to read-only using the r flag. Finally, we print the file to the console.

def read_entries():
    os.chdir(journal_name)
    journal_list = os.listdir()

    question = [{
        "type": "select",
        "name": "select_entry",
        "message": "Choose an entry to read",
        "choices": journal_list
    },]
    entry = prompt(question)['select_entry']
    with open(entry, 'r') as e:
        print(e.read())

Fancy CLI Add ons

Flags

You may want to back up your entries using git as you journal. If you are not already doing this project in a git repository, you can use the command git init to initialize a git repository and then add and commit your files. Then you can use the following commands to set a remote URL (to your GitHub repository) for your files to push to:

$ git remote add origin  <REMOTE_URL> 
# Sets the new remote
$ git remote -v
# Verifies the new remote URL

Finally, push your entries to the remote:

$ git push -u origin main
# Pushes the changes in your local repository up to the remote repository you specified as the origin

Side Note: The following about adding a remote repository and setting up gitignores will probably be helpful to you.

So what if we want to automate this with the CLI app? Flags are used as (optional) add-ons that alter how your CLI app runs. Luckily for us, plumbum makes adding another flag relatively easy. So if we use cli.Flag, the result will be attached to self, and we can use it in our program.

[...]
class GJournal(cli.Application):
    VERSION = "0.0"

    push = cli.Flag(['p', 'push'], help="Commits and pushes the added files")
[...]

This flag commits and pushes our added files. Now to use it, we can check if the user has supplied the flag. If they have, commit and push the entries at the end of the program:

from plumbum.cmd import git

[...]
class GJournal(cli.Application):
    [...]
    elif choice == 'Quit':
            print("Goodbye, have a lovely day!")

        timestamp = str(datetime.now())
        today_entry = str(datetime.today().strftime('%Y-%m-%d')) + ".txt"

        if self.push:
            git('add', today_entry)
            git('commit', '-m', timestamp + ' make update to daily entry')
            git('push')
[...]

And when we run it, we can verify using git log

$ git log
commit dd2543d
Author: Drshika Asher <>
Date:   Sun May 22 21:55:06 2022 -0500

    2022-05-22 21:55:06.091703 make update to daily entry

The help output will also show the input for the new option.

$ python journal.py --help
journal.py 0.0

Usage:
    journal.py [SWITCHES] 

Meta-switches:
    -h, --help         Prints this help message and quits
    --help-all         Prints help messages of all sub-commands and quits
    -v, --version      Prints the program's version and quits

Switches:
    -p, --push         Commits and pushes the added files

Progress Bar

This step is completely optional but if you want to give the user some idea of how far along they are with committing the files, you can use a progress bar. Since it’s relatively non-trivial to get the progress from git, we’re just going to use a dummy for loop. So let’s use the progress module from the rich library to make our progress bar:

from rich.progress import Progress
import time

# Code credit to harsh183
def display_push_progress():
    with Progress() as progress:
        push_progress = progress.add_task("Pushing files :partying_face:")

        for i in range(100):
            progress.update(push_progress, advance=1)
            time.sleep(0.05)

And then add the display_push_progress() function call below your push in the GJournal Main method.


[...]
    if self.push:
        git('add', today_entry)
        git('commit', '-m', timestamp + ' make update to daily entry')
        git('push')
        display_push_progress()
[...]

Here’s everything you should have in this part:

import time

def display_push_progress():
    # Note: Actually getting progress from git is surprisingly non-trivial
    # So I've hard coded an example for now, but in real code, emit things as they happen
    with Progress() as progress:
        push_progress = progress.add_task("Pushing files :partying_face:")

        for i in range(100):
            progress.update(push_progress, advance=1)
            time.sleep(0.05)

class GJournal(cli.Application):
    VERSION = "0.0"

    push = cli.Flag(['p', 'push'], help="Commits and pushes the added files")

    def main(self):
        global journal_name,author,push

        load_config("config.yaml")
        print_banner("Gratitude Journal")
    
        choice = questionary.select(
        "What would you like to do",
        choices=[
            'Journal',
            'Read Entries',
            'Quit'
        ]).ask()
        if choice == 'Journal':
            if journal_name == "":
                create_journal()
            else:
                open_journal()
        elif choice == 'Read Entries':
            read_entries()
        elif choice == 'Quit':
            print("Goodbye, have a lovely day!")

        timestamp = str(datetime.now())
        today_entry = str(datetime.today().strftime('%Y-%m-%d')) + ".txt"

        if self.push:
            git('add', today_entry)
            git('commit', '-m', timestamp + ' make update to daily entry')
            git('push')
            display_push_progress()

Conclusion

Final code (also viewable on github): https://github.com/drshika/sosp22-cli

import os, fnmatch
from datetime import datetime
from pyfiglet import Figlet
from plumbum import colors, cli 
import questionary
from questionary import prompt
import yaml, ruamel.yaml
from rich.progress import Progress
from plumbum.cmd import git
import textwrap
import time

def print_banner(text):
    with colors['LIGHT_SEA_GREEN']:
        print(Figlet(font='slant').renderText(text))

author = ""
journal_name = ""

# loads a journal yaml config file, creating it if missing
def load_config(filename):
    global author, journal_name
    if not os.path.exists(filename):
        save_config(filename, {
            "author": '',
            "journal_name": ''
        })

    with open(filename, "r") as file:
        data = yaml.safe_load(file)
    author = data['author']
    journal_name = data['journal_name']

# save the config to a file
def save_config(filename, config):
    yaml = ruamel.yaml.YAML()

    with open(filename, "w") as file:
            yaml.dump(config, file)

def init_folder(folder_name):
    #making the directory
    try:
        os.makedirs(folder_name)
    except OSError:
        print(f"Creating the directory {folder_name} has failed.")

    os.chdir(journal_name)
    add_page()

#(1) buy the journal
def create_journal():
    #prompt user to name journal
    global author, journal_name
    author = ruamel.yaml.scalarstring.DoubleQuotedScalarString(questionary.text("What is your name?").ask())
    
    #find the place to save the journal
    journal_name = ruamel.yaml.scalarstring.DoubleQuotedScalarString(author + "-Journal")

    my_dict = dict(author=author, journal_name=journal_name)

    save_config("config.yaml", my_dict)
    init_folder(journal_name)

def add_content(title):
    with open(title, 'a') as entry:
        writing = questionary.text("What are you grateful for?").ask()
        prettier_writing = textwrap.fill(writing) + "\n"
        entry.write(prettier_writing)

#flip to the right page
def add_page():
    today_entry = str(datetime.today().strftime('%Y-%m-%d'))+ ".txt"
    open(today_entry, 'x')
    print("Created Entry" + today_entry)
    add_content(today_entry)

#open the journal
def open_journal():
    today_entry = str(datetime.today().strftime('%Y-%m-%d')) + ".txt"

    os.chdir(journal_name)
    journal_list = os.listdir()

    #looping through the list of journals to check if there is an entry for today
    if not journal_list:
        add_page()
    for journal in journal_list:
        if fnmatch.fnmatch(journal, today_entry):
            add_content(today_entry)
        else:
            add_page()

# flip through past pages
def read_entries():
    global journal_name
    os.chdir(journal_name)
    journal_list = os.listdir()

    question = [{
        "type": "select",
        "name": "select_entry",
        "message": "Choose an entry to read",
        "choices": journal_list
    },]
    entry = prompt(question)['select_entry']
    with open(entry, 'r') as e:
        print(e.read())

# Code credit to harsh183
def display_push_progress():
    with Progress() as progress:
        push_progress = progress.add_task("Pushing files :partying_face:")

        for i in range(100):
            progress.update(push_progress, advance=1)
            time.sleep(0.05)

class GJournal(cli.Application):
    VERSION = "0.0"

    push = cli.Flag(['p', 'push'], help="Commits and pushes the added files")

    def main(self):
        global journal_name,author,push

        load_config("config.yaml")
        print_banner("Gratitude Journal")
    
        choice = questionary.select(
        "What would you like to do",
        choices=[
            'Journal',
            'Read Entries',
            'Quit'
        ]).ask()
        if choice == 'Journal':
            if journal_name == "":
                create_journal()
            else:
                open_journal()
        elif choice == 'Read Entries':
            read_entries()
        elif choice == 'Quit':
            print("Goodbye, have a lovely day!")

        timestamp = str(datetime.now())
        today_entry = str(datetime.today().strftime('%Y-%m-%d')) + ".txt"

        if self.push:
            git('add', today_entry)
            git('commit', '-m', timestamp + ' make update to daily entry')
            git('push')
            display_push_progress()

if __name__ == "__main__":
    GJournal()

Exercise

  • Add an optional command line flag (also called switch) called -c/--commit to run git commit at the end of the program.

Learn More About Fancy CLIs

Learn More Python Concepts

  • How to use APIs in Python Beginner (basic requests and responses) and then do Intermediate (headers, authentication, pagination, rate limiting, cache, combining API requests). Recommended if you’re planning to use your CLI to wrap around an API

  • Map, Filter, Reduce and List Comprehension for doing more list based stuff in python

  • os module in python to interact with the operating system

Ethics

Ideas

Feel free to come up with anything you want as long as it’s CLI related. Here are some ideas to help you get started, but feel free to come up with more. Don’t worry if it’s already been done or if someone else is doing it. The point is to learn and have fun. :)

  • Take some application you commonly use and try to make a command line version. For example, gratitude journals, weather updates or getting scores from a sports team of your choice.

  • Is there some command line application you found annoying or hard to use? How would you improve it?

  • Maybe the chatbot from last week could also be available in command line form. Could you spice it up with fancy menu selectors or ASCII displays?

  • Maybe you can use this to automate some workflow task you have by combining some steps together with plumbum. One of the greatest things about command lines is that you can use each program as a modular piece to compose very powerful utilities.

Requirements

  • Runs from your command line.

  • Has a version number.

  • Has a working -h/--help option.

  • Accepts at least one custom flag.

  • Has to have a README where you explain how it works along with pictures/animations/videos.

  • Has to use an Open Source license via a LICENSE file.

Acknowledgements

Thank you so much for joining us for this workshop. Thanks to Harsh Deep & Monica Para (cofounder of 125 Summer of Side Projects: https://125summer.tech/) and Candace Williams for a lot of language, explanations, and packages used to create this workshop. ILL!