Skip to content

Interactive with shell terminal

Assignment

Build a Python application with:

  • Handle random sentence provided by user

  • Repeat n-integer times: shuffle tokens split of sentence with color change by characters and annotate with random emoji

  • Streaming output into terminal for user

Example output:

The example output of the assignment

Solution

Techstack

Python language with following package:

  • standard - random: Generate pseudo-random numbers

  • rich: Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal

Flow

sequenceDiagram

  %% component
  participant client as Client
  participant console as Console
  participant app as Application

  %% Flow
  client->>console: Input the sentence and the repeat number
  console->>app: Handle logic implement
    loop Construct statements
        app->>app: Split tokens - Annotate emoji - Random color
    end
  app->>console: Render output into terminal
  console->>client: Yield output by live support

Step

Step 1: Declare the related required package

requirements.txt
rich>=13.7.1

Step 2: Install dependencies in vitural environment

python -m venv venv
source venv/Script/active
python -m pip install -r requirements.txt --default-timeout 100

Step 3: Build the script

  • Declare enumuration related to a random shuffle component, supported by rich

The emoji name

Enumuration: Emoj
# Emoji component: The list of supported emoji by rich
# Enumuration: `python -m rich.emoji`
RICH_EMOJI: list[str] = ["alien", "gem", "speedboat", "zzz", "droplet", "duck", "frog", "cloud"]

The color for shuffle

Enumuration: Color
# Color shuffle: The list of color to shuffle
# Enumuration: https://rich.readthedocs.io/en/latest/appendix/colors.html#appendix-colors
RICH_COLOR: list[str] = ["red", "blue", "cyan", "gold1", "grey5", "dodger_blue2", "purple4", "deep_pink4", "tan", "violet"]
  • Handle util function to render component: block text and icon

For block text with color, which follow pattern of [color]{sentence}[/color]

Function: Render block text with color
def render_block_text(sentence: str, color: str) -> str:
    """Render sentence with color"""
    return f"[{color}]{sentence}[/{color}]"

For icon, the pattern is :{icon}:

Function: Render icon
def render_icon(name: str) -> str:
    """Render icon based on name"""
    return f":{name}:"
  • Construct console client by class::Console
Construct console
    # Handle
    console = Console(emoji=True)
  • Handle user input with 2 component:

a) The sentence random inputed by user

b) The number of rows repeated (number)

Handle user input
    # Get from user
    # Inspired by: https://github.com/Textualize/rich/blob/master/rich/prompt.py
    input_sentence = Prompt.ask(":unicorn_face: Input sentence")

    # Control
    while True:
        input_number: int = IntPrompt.ask(":high_voltage: Number to repeat")
        if input_number > 0 and input_number <= 1000:
            break
        console.print(":o: Invalid number to repeat. Required positive number in range of [0, 1000]")
  • Build live output with console print, inside a for loop for each sentence

Each loop, included:

  • Split text into tokens and shuffle it

  • Choose random index that will be replaced by emoji

  • Loop within tokens:

    a) If satified random handler: return block text with random color

    b) If it's in the random index that has emoji: return the icon related

  • Merged all the tokens into 1 sentence

  • Yield the console

Loop container
    # Live output
    with Live(None, refresh_per_second=3): # Update 3 times a second to feel fluid

        # For loop
        for ind in range(0, input_number):

            # Arbitrary delay
            time.sleep(0.4)

            # Shuffle
            on_component = list(input_sentence)
            random.shuffle(on_component)

            # Shuffle icon
            ind_emoji = random.choices(range(len(on_component)), k=min(len(on_component), 5))

            # Build the component
            on_log: list[str] = []
            for ind, on_text in enumerate(on_component, start=1):

                # Random color on text
                if (int(random.random() * 10**6) + ind) % 2 == 0: # Just a random function
                    rnd_color = random.choice(RICH_COLOR)
                    on_text = render_block_text(sentence=on_text, color=rnd_color)

                # Random emoji based on random index
                elif ind in ind_emoji:
                    on_text = render_icon(name=random.choice(RICH_EMOJI))

                # Append
                on_log.append(on_text)

            # Construct
            on_log = "".join(on_log)

            # Output
            console.print(on_log)

Step 4: Run the script

python main.py
Example 1 Example 2 Example 3
Input: Rich is a Python package Input: Netflix is an American subscription video on-demand service Input: Testing leads to failure. And failure leads to understanding
Repeat: 25 Repeat: 30 Repeat: 34
example-1 example-2 example-3

Further reading

Full script

main.py
#!/bin/python3

# Global
import sys
import os
import random
import time

# Path Append
sys.path.append(os.path.abspath(os.curdir))

# External
from rich.prompt import Prompt, IntPrompt
from rich.console import Console
from rich.live import Live

# Emoji component: The list of supported emoji by rich
# Enumuration: `python -m rich.emoji`
RICH_EMOJI: list[str] = ["alien", "gem", "speedboat", "zzz", "droplet", "duck", "frog", "cloud"]

# Color shuffle: The list of color to shuffle
# Enumuration: https://rich.readthedocs.io/en/latest/appendix/colors.html#appendix-colors
RICH_COLOR: list[str] = ["red", "blue", "cyan", "gold1", "grey5", "dodger_blue2", "purple4", "deep_pink4", "tan", "violet"]



def render_block_text(sentence: str, color: str) -> str:
    """Render sentence with color"""
    return f"[{color}]{sentence}[/{color}]"




def render_icon(name: str) -> str:
    """Render icon based on name"""
    return f":{name}:"


if __name__ == "__main__":

    # Handle
    console = Console(emoji=True)

    # Get from user
    # Inspired by: https://github.com/Textualize/rich/blob/master/rich/prompt.py
    input_sentence = Prompt.ask(":unicorn_face: Input sentence")

    # Control
    while True:
        input_number: int = IntPrompt.ask(":high_voltage: Number to repeat")
        if input_number > 0 and input_number <= 1000:
            break
        console.print(":o: Invalid number to repeat. Required positive number in range of [0, 1000]")


    # Live output
    with Live(None, refresh_per_second=3): # Update 3 times a second to feel fluid

        # For loop
        for ind in range(0, input_number):

            # Arbitrary delay
            time.sleep(0.4)

            # Shuffle
            on_component = list(input_sentence)
            random.shuffle(on_component)

            # Shuffle icon
            ind_emoji = random.choices(range(len(on_component)), k=min(len(on_component), 5))

            # Build the component
            on_log: list[str] = []
            for ind, on_text in enumerate(on_component, start=1):

                # Random color on text
                if (int(random.random() * 10**6) + ind) % 2 == 0: # Just a random function
                    rnd_color = random.choice(RICH_COLOR)
                    on_text = render_block_text(sentence=on_text, color=rnd_color)

                # Random emoji based on random index
                elif ind in ind_emoji:
                    on_text = render_icon(name=random.choice(RICH_EMOJI))

                # Append
                on_log.append(on_text)

            # Construct
            on_log = "".join(on_log)

            # Output
            console.print(on_log)