#!/usr/bin/env bash #==============================================================# # # AUTHOR: # Bryan Jenks # - www.bryanjenks.xyz # - https://github.com/tallguyjenks/flash.sh # # SOURCE: # This file is based off of the one presented in this YouTube Video by nixcasts: # - https://www.youtube.com/watch?v=lX8jqo70r1I # # PURPOSE: # To have a command line flash card tool with minimal code, plain text input and output # # VISION: # The goal i have for this script is a basic level emulation of ANKI to where i have a way to # keep track of a score for each item in each selected deck so that it can pick from a selection # of the lowest scoring items and shuf them to the user for reenforcement of active recall. # # DEPENDENCIES: # fzf - https://github.com/junegunn/fzf # bat - https://github.com/sharkdp/bat #==============================================================# # USER CUSTOMIZABLE VARIABLES CARD_POOL_SIZE=10 # How large the pool size is for shuf to draw from SEARCH_DEPTH=999 # How many levels to recursively search for .csv's in .local/share/flash # ANSI FOREGROUND ESCAPE COLORS RED='\033[0;31m' LRED='\033[1;31m' YELLOW='\033[1;33m' GREEN='\033[0;32m' LGREEN='\033[1;32m' BLUE='\033[0;34m' LBLUE='\033[1;34m' CYAN='\033[0;36m' LCYAN='\033[1;36m' ORANGE='\033[0;33m' LGREY='\033[0;37m' WHITE='\033[1;37m' NC='\033[0m' # No Color # ANSI BACKGROUND ESCAPE COLORS WHITEBG='\033[1;47m' # FONT FORMAT EXCAPE CODES BOLD='\e[1m' # Remember User's Starting Directory PWD="$(pwd)" # Where Decks Are Located for linux & mac # Prefer XDG Configuration before hard coded file paths OS="$(uname)" case "$OS" in "Darwin") DIR="${XDG_DATA_HOME:-HOME/Library/Application Support}/flash" ;; "Linux") DIR="${XDG_DATA_HOME:-$HOME/.local/share}/flash" ;; *) DIR="${XDG_DATA_HOME:-$HOME/.local/share}/flash" ;; esac # Where the example deck will be placed and named EXAMPLE_DECK="$DIR/deck.csv" # Track High score HIGH_SCORE="$DIR/.highscore" # Track number of cards reviewed per session REVIEW_LOG="$DIR/.reviews" # Iterator for Count of cards reveiwed COUNTER=0 # Success message of setup process DIR_MADE_MSG=" Your ${LRED}$DIR${NC} directory has been made and your ${ORANGE}deck.csv${NC} file is ready for you to enter your flashcard data " # User has .local/share directory but no decks inside NO_DECKS=" No decks were found, please make a new deck using ${ORANGE}:${NC} as a delimiter in a ${ORANGE}.csv${NC} file in the ${LRED}$DIR${NC} directory. An example of a card: ${GREEN}Math:What is the square root of 4?:2:0${NC} " # The example flashcard deck to be made DECK_TEMPLATE="History:When was the declaration of independence signed?:1776:0 Math:What is the square root of 4?:2:4 Science:What is the charge on a proton?:Positive 1:2 Philosophy:What was Socrates known as?:The Gadfly of Athens:0 Programming:What is the typical starting index of an array?:0:5 History:What did Abraham Lincoln typically keep in his hat?:Mail:0 Math:What is the value of PI to 2 decimal places?:3.14:4 Science:What is the charge on an electron?:Negative 1:3 Programming:What does OOP stand for?:Object Oriented Programming:2 History:What did Socrates drink to commit suicide?:Hemlock Tea:2 Math:What is the general equation for the slope of a line?:y=mx+b:4 Science:What is the charge on a neutron?:Neutral:1 Programming:What is Vim?:God's Text Editor:999 History:What were the British known by during the American Revolution?:The Redcoats:1 Math:What is the value of this equation - log10(100)?:2:0 Science:What are protons and neutrons made of?:quarks:5 Programming:What does RAM stand for?:Random Access Memory:1 History:What was the year 2000 also known as?:Y2K:0 Math:What is the formula for the mean?:Sum/count:4 Science:What is cold?:The absense of heat:3 Programming:What languages are the worst?:Proprietary:999 History:When did man land on the moon?:1969:4 Math:10^3=?:1000:1 Science:The _____ ______ Project mapped all of man's genes.:Human Genome:3 Programming:What is the best computer to program on?:Thinkpad:999 History:When was fla.sh created?:April 2020:999 Math:What do you call a number only divisible by itself and 1?:Prime:0 Science:What is the distance between the Earth and Sol called?:An Astronomical Unit (AU):1 Programming:What is the best operating system?:Arch, because BTW i run Arch:999" # Define setup process in a function and create necessary files for user setup(){\ mkdir "$DIR" && \ eval touch {"$EXAMPLE_DECK","$HIGH_SCORE","$REVIEW_LOG"} && \ echo "$DECK_TEMPLATE" >> "$EXAMPLE_DECK" && \ echo -e "$DIR_MADE_MSG" \ ;} # Test if .local/share exists and wether to offer setup process if [ ! -d "$DIR" ];then echo -e "No ${LRED}$DIR${NC} directory, make it? ${LGREEN}Y${NC}/${LRED}N${NC}" # shellcheck disable=SC2162 read RESPONSE case "$RESPONSE" in [QqNn]) exit ;; [Yy]) setup && exit ;; *) echo -e "invalid choice, please select either ${LGREEN}y${NC} or ${LRED}n${NC}" && exit ;; esac fi # go to the flashcard decks directory cd "$DIR" || exit # If there are no flashcard decks available return user to starting location # while also displaying explanatory text of issue if [ "$(find . -maxdepth "$SEARCH_DEPTH" -iname "*.csv" | wc -l)" = 0 ]; then echo -e "$NO_DECKS" && cd "$PWD" && exit fi # if highscore file was removed, remake it. if [ ! -e "$HIGH_SCORE" ]; then touch "$HIGH_SCORE" fi # if reviewed file was removed, remake it. if [ ! -e "$REVIEW_LOG" ]; then touch "$REVIEW_LOG" fi a_flag='' b_flag='' print_usage() { printf "\n${LCYAN}fla.sh --- Flash card system by Bryan Jenks${NC} ${LBLUE}${NC}\n\n${YELLOW}${BOLD}Usage:${NC}\n\t${GREEN}flash -h:${NC} Print this help text\n\t${GREEN}flash -i:${NC} Print Information about the flashcard system\n\t${GREEN}flash -v:${NC} Print version Number\n\n" } while getopts 'hiv' flag; do case "${flag}" in h) print_usage && exit ;; i) a_flag='true' ;; v) b_flag='true' ;; *) print_usage && exit 1 ;; esac done # Show pretty FZF preview of decks using BAT DECK="$(find . -maxdepth "$SEARCH_DEPTH" -iname "*.csv" | fzf --preview='bat --theme="Solarized (dark)" --style=numbers --color=always {} | head -500')" # If no deck is selected in fzf menu # return user to start location # and exit quietly if [ -z "$DECK" ]; then cd "$PWD" && exit fi main(){ IFS=$':' # shellcheck disable=SC2162 read -a q <<< "$(sort "$DECK" -n --field-separator=: --key=4 | head -n "$CARD_POOL_SIZE"| shuf -n 1)" clear echo -e "${WHITEBG} Fla.sh - Flash Cards In Your Terminal ${NC}" echo -e "${ORANGE}${BOLD}Cards Reviewed:${BOLD}${NC}\t$COUNTER" echo -e "\t${ORANGE}${BOLD}High Score:${BOLD}${NC}\t$(cat "$HIGH_SCORE")" echo -e "\t${ORANGE}${BOLD}Avg review:${BOLD}${NC}\t$(awk '{ sum += $7; n++ } END { if (n > 0) print sum / n; }' "$REVIEW_LOG")" echo -e "" echo -e "${LGREY}Category:${NC}\n\t\t${q[0]}" echo -e "${LGREY}Question:${NC}\n\t\t${q[1]}" echo -e "" echo -e "" echo -e "${LGREY}Press [Enter] to continue...${NC}" # shellcheck disable=SC2162 read -sn 1 NEXT while [ ! "$NEXT" = "" ] && [ ! "$NEXT" = q ] && [ ! "$NEXT" = Q ]; do # shellcheck disable=SC2162 read -sn 1 NEXT done clear echo -e "${WHITEBG} Fla.sh - Flash Cards In Your Terminal ${NC}" echo -e "${ORANGE}${BOLD}Cards Reviewed:${BOLD}${NC}\t$COUNTER" echo -e "\t${ORANGE}${BOLD}High Score:${BOLD}${NC}\t$(cat "$HIGH_SCORE")" echo -e "\t${ORANGE}${BOLD}Avg review:${BOLD}${NC}\t$(awk '{ sum += $7; n++ } END { if (n > 0) print sum / n; }' "$REVIEW_LOG")" echo -e "" echo -e "${LGREY}Category:${NC}\n\t\t${q[0]}" echo -e "${LGREY}Question:${NC}\n\t\t${q[1]}" echo -e "" if [ "$NEXT" = q ] || [ "$NEXT" = Q ]; then add_usage_entry && cd "$PWD" && clear && exit fi echo -e "${LGREY}Answer:${NC}\n\t\t${q[2]}" echo -e "" echo -e "${WHITEBG}${WHITE}===========================================================${NC}" echo -e "" echo -e "${LGREY}How Difficult Was This Question?${NC}" echo -e "" echo -e "${LRED}Hard${NC} [1] ${RED}Difficult${NC} [2] ${YELLOW}Normal${NC} [3] ${GREEN}Mild${NC} [4] ${LGREEN}Easy${NC} [5]" echo -e "" echo -e "${LGREY}Select a number to continue, or${NC} ${LRED}Q${NC} ${LGREY}to quit...${NC}" # shellcheck disable=SC2162 read -sn 1 DIFFICULTY_SCORE while [[ ! "$DIFFICULTY_SCORE" =~ [12345qQ] ]]; do # shellcheck disable=SC2162 read -sn 1 DIFFICULTY_SCORE done if [ "$DIFFICULTY_SCORE" = q ] || [ "$DIFFICULTY_SCORE" = Q ]; then add_usage_entry && cd "$PWD" && clear && exit fi clear COUNTER="$((COUNTER+1))" # Increment count for card review count increment if [ "${q[3]}" = 0 ]; then NEW_ITEM_SCORE=0 case "$DIFFICULTY_SCORE" in [123]) NEW_ITEM_SCORE=0 ;;#HARD DIFFICULTY & NORMAL 4) NEW_ITEM_SCORE=1 ;;#MILD 5) NEW_ITEM_SCORE=2 ;;#EASY *) NEW_ITEM_SCORE=0 ;;#INVALID esac elif [ "${q[3]}" = 1 ]; then case "$DIFFICULTY_SCORE" in 1) NEW_ITEM_SCORE=0 ;;#HARD 2) NEW_ITEM_SCORE=0 ;;#DIFFICULT 3) NEW_ITEM_SCORE=1 ;;#NORMAL 4) NEW_ITEM_SCORE=2 ;;#MILD 5) NEW_ITEM_SCORE=3 ;;#EASY *) NEW_ITEM_SCORE=1 ;;#INVALID esac else case "$DIFFICULTY_SCORE" in 1) NEW_ITEM_SCORE="$((q[3]-2))" ;;#HARD 2) NEW_ITEM_SCORE="$((q[3]-1))" ;;#DIFFICULTY 3) NEW_ITEM_SCORE="${q[3]}" ;;#NORMAL 4) NEW_ITEM_SCORE="$((q[3]+1))" ;;#MILD 5) NEW_ITEM_SCORE="$((q[3]+2))" ;;#EASY *) NEW_ITEM_SCORE="${q[3]}" ;;#INVALID esac fi # Update item score for each flashcard item sed -i "s/${q[0]}:${q[1]}:${q[2]}:${q[3]}/${q[0]}:${q[1]}:${q[2]}:$NEW_ITEM_SCORE/g" "$DECK" # If no highscore currently set, set it. if [ -z "$(cat "$HIGH_SCORE")" ]; then echo "$COUNTER" > "$HIGH_SCORE" fi # If Cards Reviewed > Current High Score, Update if [ "$COUNTER" -gt "$(cat "$HIGH_SCORE")" ]; then echo "$COUNTER" > "$HIGH_SCORE" fi } add_usage_entry(){ if [ ! "$COUNTER" = 0 ]; then # Create a New Entry TIME_STAMP=$(date --rfc-3339=seconds) REVIEWED_DECK="$(echo "$EXAMPLE_DECK" | awk -F/ '{print $7}')" printf -v ENTRY "TimeStamp: %s Deck: %s cardsReviewed: %s" "$TIME_STAMP" "$REVIEWED_DECK" "$COUNTER" echo "$ENTRY" >> "$REVIEW_LOG" fi } while true; do main done