354 lines
14 KiB
Bash
Executable File
354 lines
14 KiB
Bash
Executable File
#!/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
|
|
PREVIEWER='bat' ########## What fzf previewer to use when searching through decks
|
|
CURR_DECK_DISPLAY="file" # Options are 'file' or 'path'
|
|
|
|
# ANSI FOREGROUND ESCAPE COLORS
|
|
RED='\033[0;31m'
|
|
LRED='\033[1;31m'
|
|
YELLOW='\033[1;33m'
|
|
GREEN='\033[0;32m'
|
|
LGREEN='\033[1;32m'
|
|
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" && alias shuf=gshuf ;;
|
|
"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
|
|
If you want to see information about ${ORANGE}fla.sh${NC} then use the option
|
|
${YELLOW}flash -i${NC}.
|
|
"
|
|
# 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" && \
|
|
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 0 ;;
|
|
[Yy]) setup && exit 0 ;;
|
|
*) echo -e "invalid choice, please select either ${LGREEN}y${NC} or ${LRED}n${NC}" && exit 1 ;;
|
|
esac
|
|
fi
|
|
|
|
# go to the flashcard decks directory
|
|
cd "$DIR" || exit 1
|
|
|
|
# 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 1
|
|
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
|
|
|
|
print_usage() {
|
|
echo -e "\n${LCYAN}fla.sh --- Flash card system by Bryan Jenks${NC} ${LBLUE}<BryanJenks@protonmail.com>${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\t${GREEN}flash -p [BINARY]:${NC} Change the previewer when selecting decks.\n\t\tDefault: ${GREEN}bat${NC}\n\t\tSupported: ${GREEN}bat${NC}, ${GREEN}cat${NC}\n"
|
|
}
|
|
|
|
print_info() {
|
|
echo -e "\nThis flash card system works via colon ${YELLOW}:${NC} seperated ${YELLOW}.csv${NC} files\n
|
|
with entries that look like this: ${LGREY}category:question:answer:0${NC}\n
|
|
These ${YELLOW}.csv${NC} files should be stored in your
|
|
|
|
\t${YELLOW}\$XDG_DATA_HOME/flash${NC}
|
|
OR
|
|
\t${YELLOW}~/.local/share/flash${NC} for Linux
|
|
\t${YELLOW}~/Library/Application Support/flash${NC} for mac
|
|
-------------------------------------------------------------------------------
|
|
${LCYAN}Exiting:${NC}\n
|
|
\tYou can exit the application at any time by pressing either ${RED}q${NC} or ${RED}Q${NC}
|
|
|
|
${LCYAN}Usage:${NC}\n
|
|
\tYou will first be prompted with a question and will need to press ${RED}[Enter]${NC}
|
|
\tto reveal the answer. Once the answer is revealed you will need to either
|
|
\tquit, or give a rating to how difficult the question was.
|
|
|
|
${LCYAN}The Scoring System:${NC}\n
|
|
\tThe last field in the ${YELLOW}.csv${NC} files is the current point score of the 'card'
|
|
\tEvery time you rate a card the point score will either increase, decrease,
|
|
\tor stay the same. zero is the lowest value, and the upper limit is over
|
|
\t100 billion.
|
|
|
|
\t${CYAN}Scoring Results:${NC}
|
|
\t\t[${LRED}Hard${NC}]\t\t-2 points
|
|
\t\t[${RED}Difficult${NC}]\t-1 point
|
|
\t\t[${YELLOW}Normal${NC}]\tNo Change
|
|
\t\t[${GREEN}Mild${NC}]\t\t+1 point
|
|
\t\t[${LGREEN}Easy${NC}]\t\t+2 points
|
|
|
|
\tThe cards with the lowest scores (you scored them as ${LRED}Hard${NC}/${RED}Difficult${NC}) are
|
|
\tsorted to the top, a pool of them are picked and from that pool 1 card is
|
|
\trandomly drawn. As you become more familair with the material and rate it
|
|
\tas easier, the point values will go up and the cards will appear less
|
|
\tfrequently making room for those cards that are still difficult and have
|
|
\tlower point values.
|
|
|
|
It is a good idea to start all cards off at ${YELLOW}0${NC} initially so that they all
|
|
have an equal chance of being drawn initially.
|
|
"
|
|
}
|
|
|
|
while getopts 'hivp:' flag; do
|
|
case "${flag}" in
|
|
h) print_usage && exit 0 ;;
|
|
i) print_info && exit 0 ;;
|
|
v) echo -e "\n${YELLOW}fla.sh Current Version:${NC} ${RED}1.1${NC}\n" && exit 0 ;;
|
|
p) if [[ $(command -v "$OPTARG" 2>&1) ]]; then PREVIEWER=$OPTARG; else echo "Unable to find previewer $OPTARG. Exiting..." && exit 1; fi ;;
|
|
*) print_usage && exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
|
|
# Set some parameters for preview command used by FZF
|
|
while [ -z "$PREVIEWER_PARAMTERS" ]; do
|
|
case "$PREVIEWER" in
|
|
bat) PREVIEWER_PARAMTERS="--theme='Solarized (dark)' --style=numbers --color=always" ;;
|
|
cat) PREVIEWER_PARAMTERS="--number" ;;
|
|
*) echo -e "${RED}$PREVIEWER${NC} is not a valid previewer. Use '${GREEN}bat${NC}' or '${GREEN}cat${NC}'. Falling back on default...\n" && read -r -s -p 'Press [Enter] to continue...' && echo -e "\n" && PREVIEWER='bat' ;;
|
|
esac
|
|
done
|
|
|
|
# Show pretty FZF preview of decks using $PREVIEWER. Default: BAT
|
|
DECK="$(find . -maxdepth "$SEARCH_DEPTH" -iname "*.csv" | fzf --preview="$PREVIEWER $PREVIEWER_PARAMTERS {} | head -500")"
|
|
|
|
# Get current deck name for display
|
|
case "$CURR_DECK_DISPLAY" in
|
|
file) CURRENT_DECK="$(echo "$DECK" | awk -F/ '{print $NF}')" ;;
|
|
path) CURRENT_DECK="$DECK" ;;
|
|
*) CURRENT_DECK="$(echo "$DECK" | awk -F/ '{print $NF}')" ;;
|
|
esac
|
|
|
|
|
|
|
|
|
|
# If no deck is selected in fzf menu
|
|
# return user to start location
|
|
# and exit quietly
|
|
if [ -z "$DECK" ]; then
|
|
clear && cd "$PWD" && exit 1
|
|
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}
|
|
${ORANGE}${BOLD} Current Deck:${BOLD}${NC}\t$CURRENT_DECK
|
|
${ORANGE}${BOLD}Cards Reviewed:${BOLD}${NC}\t$COUNTER
|
|
\t${ORANGE}${BOLD}High Score:${BOLD}${NC}\t$(cat "$HIGH_SCORE")
|
|
\t${ORANGE}${BOLD}Avg review:${BOLD}${NC}\t$(awk '{ sum += $7; n++ } END { if (n > 0) print sum / n; }' "$REVIEW_LOG")
|
|
|
|
${LGREY}Category:${NC}\n\t\t${q[0]}
|
|
${LGREY}Question:${NC}\n\t\t${q[1]}
|
|
|
|
|
|
${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}
|
|
${ORANGE}${BOLD} Current Deck:${BOLD}${NC}\t$CURRENT_DECK
|
|
${ORANGE}${BOLD}Cards Reviewed:${BOLD}${NC}\t$COUNTER
|
|
\t${ORANGE}${BOLD}High Score:${BOLD}${NC}\t$(cat "$HIGH_SCORE")
|
|
\t${ORANGE}${BOLD}Avg review:${BOLD}${NC}\t$(awk '{ sum += $7; n++ } END { if (n > 0) print sum / n; }' "$REVIEW_LOG")
|
|
|
|
${LGREY}Category:${NC}\n\t\t${q[0]}
|
|
${LGREY}Question:${NC}\n\t\t${q[1]}
|
|
"
|
|
if [ "$NEXT" = q ] || [ "$NEXT" = Q ]; then
|
|
add_usage_entry && cd "$PWD" && clear && exit 0
|
|
fi
|
|
echo -e "${LGREY}Answer:${NC}\n\t\t${q[2]}
|
|
|
|
${WHITEBG}${WHITE}===========================================================${NC}
|
|
|
|
${LGREY}How Difficult Was This Question?${NC}
|
|
|
|
${LRED}Hard${NC} [1] ${RED}Difficult${NC} [2] ${YELLOW}Normal${NC} [3] ${GREEN}Mild${NC} [4] ${LGREEN}Easy${NC} [5]
|
|
|
|
${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 0
|
|
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
|