From e7781bf70f656c6b371e12bc7e599cc0e93b922e Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 4 Jan 2024 23:59:44 -0500 Subject: [PATCH 01/76] added helpers for make --- Dockerfile | 3 +- etc/profile.d/help50.sh | 66 +++++++++++++++++++++++++++++++++++++ opt/cs50/bin/make | 35 +++++++++----------- opt/cs50/lib/help50/cd.sh | 9 +++++ opt/cs50/lib/help50/ls.sh | 6 ++++ opt/cs50/lib/help50/make.sh | 42 +++++++++++++++++++++++ tests/bar.c | 1 + tests/foo/baz.c | 0 8 files changed, 142 insertions(+), 20 deletions(-) create mode 100644 etc/profile.d/help50.sh mode change 100755 => 100644 opt/cs50/bin/make create mode 100644 opt/cs50/lib/help50/cd.sh create mode 100644 opt/cs50/lib/help50/ls.sh create mode 100644 opt/cs50/lib/help50/make.sh create mode 100644 tests/bar.c create mode 100644 tests/foo/baz.c diff --git a/Dockerfile b/Dockerfile index ae2d949..374c0da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -153,10 +153,12 @@ RUN curl https://packagecloud.io/install/repositories/cs50/repo/script.deb.sh | bash-completion \ build-essential `# dpkg-dev, libc, gcc, g++, make, etc.`\ clang \ + colorized-logs `# For help50` \ coreutils `# For fold` \ cowsay \ dos2unix \ dnsutils `# For nslookup` \ + expect `# For help50` \ fonts-noto-color-emoji `# For render50` \ gdb \ git \ @@ -188,7 +190,6 @@ RUN curl https://packagecloud.io/install/repositories/cs50/repo/script.deb.sh | cs50 \ Flask \ Flask-Session \ - help50 \ pytest \ render50 \ setuptools \ diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh new file mode 100644 index 0000000..836a037 --- /dev/null +++ b/etc/profile.d/help50.sh @@ -0,0 +1,66 @@ +# Directory with helpers +HELPERS="/opt/cs50/lib/help50" + +# TEMP +alias make="help50 make" + +# Formatting +bold=$(tput bold) +normal=$(tput sgr0) + + +help50() { + + # Check for helper + if [[ $# -gt 0 && ! -f "${HELPERS}/${1}.sh" ]]; then + echo "Sorry, ${bold}help50${normal} does not yet know how to help with this!" + return 1 + fi + + # Duplicate file descriptors + exec 3>&1 4>&2 + + # Redirect output to a file too + local file="/tmp/help50.$$" # Use PID to support multiple terminals + exec > >(tee -a "$file") 2>&1 + + # Execute command + if [[ "$(type -P -t "$1")" == "file" ]]; then + unbuffer "$@" # Else, e.g., ls isn't colorized + else + "$@" # Can't unbuffer builtins (e.g., cd) + fi + + # Remember status + local status=$? + + # Remember command + local command="$1" + + # Remove command from $@ + shift + + # Get redirected output + local output=$(cat "$file") + + # Remove any ANSI codes + output=$(echo "$output" | ansi2txt | col -b) + + # Restore file descriptors + exec 1>&3 2>&4 + + # Close file descriptors + exec 3>&- 4>&- + + # Remove file + rm --force "$file" + + # Preserve command's status for helpers + (exit $status) + + # Try to get help + local help=$( . "${HELPERS}/${command}.sh" <<< "$output" ) + if [[ -n "$help" ]]; then + echo "🦆 $help" + fi +} diff --git a/opt/cs50/bin/make b/opt/cs50/bin/make old mode 100755 new mode 100644 index 4658e3e..d4275e8 --- a/opt/cs50/bin/make +++ b/opt/cs50/bin/make @@ -1,23 +1,20 @@ #!/bin/bash -# Ensure no targets end with .c -args="" -invalid_args=0 -for arg; do - case "$arg" in - (*.c) arg=${arg%.c}; invalid_args=1;; - esac - args="$args $arg" -done -if [ $invalid_args -eq 1 ]; then - echo "Did you mean 'make$args'?" - exit 1 -fi +# If a single target and not an option +if [[ $# -eq 1 ]] && [[ "$1" != -* ]]; then + + # If no Makefile + if [[ ! -f Makefile && ! -f makefile ]]; then -# Run make -if [[ -d "$1" ]]; then - echo "$1 is a directory" - exit 1 -else - /usr/bin/make -B -s $* + # If target ends with .c or is a directory + if [[ "$1" == *?.c || -d "$1" ]]; then + + # Don't suppress "Nothing to be done" with --silent + /usr/bin/make "$1" + exit $? + fi + fi fi + +# Don't echo recipes +/usr/bin/make --always-make --silent "$@" diff --git a/opt/cs50/lib/help50/cd.sh b/opt/cs50/lib/help50/cd.sh new file mode 100644 index 0000000..01e9087 --- /dev/null +++ b/opt/cs50/lib/help50/cd.sh @@ -0,0 +1,9 @@ +echo "EXIT: $?" +local stdin=$(cat) +#echo "stdout[$stdout]" +#echo "1[$1]" +#echo "2[$2]" +#echo "pwd[$PWD]" +#echo "STDOUT:$stdout" +echo "STDIN:$stdin" +return 0 diff --git a/opt/cs50/lib/help50/ls.sh b/opt/cs50/lib/help50/ls.sh new file mode 100644 index 0000000..ce456b2 --- /dev/null +++ b/opt/cs50/lib/help50/ls.sh @@ -0,0 +1,6 @@ +echo "argv:[$argv]" +echo "stdout[$stdout]" +echo "1[$1]" +echo "2[$2]" +echo "pwd[$PWD]" +return 0 diff --git a/opt/cs50/lib/help50/make.sh b/opt/cs50/lib/help50/make.sh new file mode 100644 index 0000000..5b64739 --- /dev/null +++ b/opt/cs50/lib/help50/make.sh @@ -0,0 +1,42 @@ +local output=$(cat) + +local regex="make: Nothing to be done for '(.*)'" +if [[ "$output" =~ $regex ]]; then + + # If target is a directory + if [[ -d "$1" ]]; then + echo "Cannot run ${bold}make${normal} on a directory. Did you mean to ${bold}cd ${1}${normal} first?" + return + fi + + # If target ends with .c + if [[ "$1" == *?.c ]]; then + base="${1%.c}" + if [[ -n "$base" && ! -d "$base" ]]; then + echo "Did you mean to ${bold}make ${base}${normal}?" + return + fi + fi + +fi + +local regex="No rule to make target '(.*)'" +if [[ "$output" =~ $regex ]]; then + + # If no .c file for target + local c="$1.c" + if [[ ! -f "$c" ]]; then + + # Search recursively for .c file + paths=$(find * -name "$c" 2> /dev/null) + lines=$(echo "$paths" | grep -c .) + echo -n "There isn't a file called ${bold}${c}${normal} in your current directory." + if [[ "$lines" -eq 1 ]]; then # If unambiguous + d=$(dirname "$paths") + echo " Did you mean to ${bold}cd ${d}${normal} first?" + else + echo + fi + return + fi +fi diff --git a/tests/bar.c b/tests/bar.c new file mode 100644 index 0000000..b552c8e --- /dev/null +++ b/tests/bar.c @@ -0,0 +1 @@ +int main(void) {} diff --git a/tests/foo/baz.c b/tests/foo/baz.c new file mode 100644 index 0000000..e69de29 From 1f25bf5be623035557cb276ec599f8359123f96e Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 5 Jan 2024 18:35:02 -0500 Subject: [PATCH 02/76] tidying helper framework --- etc/profile.d/help50.sh | 52 +++++++++++++++++++++++++++---------- opt/cs50/bin/cp | 3 +++ opt/cs50/bin/curl | 3 +++ opt/cs50/bin/make | 0 opt/cs50/lib/help50/cd.sh | 9 ------- opt/cs50/lib/help50/ls.sh | 6 ----- opt/cs50/lib/help50/make.sh | 6 ++--- 7 files changed, 48 insertions(+), 31 deletions(-) create mode 100755 opt/cs50/bin/cp create mode 100755 opt/cs50/bin/curl mode change 100644 => 100755 opt/cs50/bin/make diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 836a037..e468df7 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -1,14 +1,21 @@ # Directory with helpers HELPERS="/opt/cs50/lib/help50" -# TEMP -alias make="help50 make" +# Temporary files +FILE="/tmp/help50.$$" # Use PID to support multiple terminals +HELP="${FILE}.help" +OUTPUT="${FILE}.output" + +# Supported helpers +#for helper in "$HELPERS"/*.sh; do +# command=$(basename "$helper" .sh) +# echo "$command"="help50 $command" +#done # Formatting bold=$(tput bold) normal=$(tput sgr0) - help50() { # Check for helper @@ -21,8 +28,7 @@ help50() { exec 3>&1 4>&2 # Redirect output to a file too - local file="/tmp/help50.$$" # Use PID to support multiple terminals - exec > >(tee -a "$file") 2>&1 + exec > >(tee -a "$FILE") 2>&1 # Execute command if [[ "$(type -P -t "$1")" == "file" ]]; then @@ -31,17 +37,15 @@ help50() { "$@" # Can't unbuffer builtins (e.g., cd) fi - # Remember status + # Remember these local status=$? - - # Remember command local command="$1" # Remove command from $@ shift - # Get redirected output - local output=$(cat "$file") + # Get tee'd output + local output=$(cat "$FILE") # Remove any ANSI codes output=$(echo "$output" | ansi2txt | col -b) @@ -52,15 +56,37 @@ help50() { # Close file descriptors exec 3>&- 4>&- - # Remove file + # Remove tee'd output rm --force "$file" - # Preserve command's status for helpers + # Restore $? to that of original command (exit $status) # Try to get help local help=$( . "${HELPERS}/${command}.sh" <<< "$output" ) if [[ -n "$help" ]]; then - echo "🦆 $help" + echo "$help" > "$HELP" + elif [[ $status -ne 0 ]]; then + echo "$output" > "$OUTPUT" + fi +} + +_help50() { + #history -a + if [[ -f "$HELP" ]]; then + echo -n "🦆 " + cat "$HELP" + rm --force "$HELP" + elif [[ -f "$OUTPUT" ]]; then + echo non-zero but no helper + rm --force "$OUTPUT" + else + echo zero fi } + +_help50() { + echo 222 +} + +export PROMPT_COMMAND=_help50 diff --git a/opt/cs50/bin/cp b/opt/cs50/bin/cp new file mode 100755 index 0000000..9a2ab78 --- /dev/null +++ b/opt/cs50/bin/cp @@ -0,0 +1,3 @@ +#!/bin/bash + +cp --interactive "$@" diff --git a/opt/cs50/bin/curl b/opt/cs50/bin/curl new file mode 100755 index 0000000..0f6171c --- /dev/null +++ b/opt/cs50/bin/curl @@ -0,0 +1,3 @@ +#!/bin/bash + +curl --http2 "$@" diff --git a/opt/cs50/bin/make b/opt/cs50/bin/make old mode 100644 new mode 100755 diff --git a/opt/cs50/lib/help50/cd.sh b/opt/cs50/lib/help50/cd.sh index 01e9087..e69de29 100644 --- a/opt/cs50/lib/help50/cd.sh +++ b/opt/cs50/lib/help50/cd.sh @@ -1,9 +0,0 @@ -echo "EXIT: $?" -local stdin=$(cat) -#echo "stdout[$stdout]" -#echo "1[$1]" -#echo "2[$2]" -#echo "pwd[$PWD]" -#echo "STDOUT:$stdout" -echo "STDIN:$stdin" -return 0 diff --git a/opt/cs50/lib/help50/ls.sh b/opt/cs50/lib/help50/ls.sh index ce456b2..e69de29 100644 --- a/opt/cs50/lib/help50/ls.sh +++ b/opt/cs50/lib/help50/ls.sh @@ -1,6 +0,0 @@ -echo "argv:[$argv]" -echo "stdout[$stdout]" -echo "1[$1]" -echo "2[$2]" -echo "pwd[$PWD]" -return 0 diff --git a/opt/cs50/lib/help50/make.sh b/opt/cs50/lib/help50/make.sh index 5b64739..136c8f0 100644 --- a/opt/cs50/lib/help50/make.sh +++ b/opt/cs50/lib/help50/make.sh @@ -1,6 +1,6 @@ -local output=$(cat) +output=$(cat) -local regex="make: Nothing to be done for '(.*)'" +regex="make: Nothing to be done for '(.*)'" if [[ "$output" =~ $regex ]]; then # If target is a directory @@ -20,7 +20,7 @@ if [[ "$output" =~ $regex ]]; then fi -local regex="No rule to make target '(.*)'" +regex="No rule to make target '(.*)'" if [[ "$output" =~ $regex ]]; then # If no .c file for target From f92afe5216d9c2f939102c1e1776e19cdde1e7b3 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 5 Jan 2024 20:05:18 -0500 Subject: [PATCH 03/76] using functions instead of aliases --- etc/profile.d/help50.sh | 20 +++++++++----------- opt/cs50/lib/help50/cd | 1 + opt/cs50/lib/help50/cd.sh | 0 opt/cs50/lib/help50/ls | 1 + opt/cs50/lib/help50/ls.sh | 0 opt/cs50/lib/help50/{make.sh => make} | 2 ++ 6 files changed, 13 insertions(+), 11 deletions(-) create mode 100755 opt/cs50/lib/help50/cd delete mode 100644 opt/cs50/lib/help50/cd.sh create mode 100755 opt/cs50/lib/help50/ls delete mode 100644 opt/cs50/lib/help50/ls.sh rename opt/cs50/lib/help50/{make.sh => make} (98%) mode change 100644 => 100755 diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index e468df7..3a023f5 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -6,11 +6,13 @@ FILE="/tmp/help50.$$" # Use PID to support multiple terminals HELP="${FILE}.help" OUTPUT="${FILE}.output" +alias cd="HOME=/tmp cd" + # Supported helpers -#for helper in "$HELPERS"/*.sh; do -# command=$(basename "$helper" .sh) -# echo "$command"="help50 $command" -#done +for helper in "$HELPERS"/*; do + name=$(basename "$helper") + eval "function ${name}() { help50 "$name" \"\$@\"; }" +done # Formatting bold=$(tput bold) @@ -19,7 +21,7 @@ normal=$(tput sgr0) help50() { # Check for helper - if [[ $# -gt 0 && ! -f "${HELPERS}/${1}.sh" ]]; then + if [[ $# -gt 0 && ! -f "${HELPERS}/${1}" ]]; then echo "Sorry, ${bold}help50${normal} does not yet know how to help with this!" return 1 fi @@ -34,7 +36,7 @@ help50() { if [[ "$(type -P -t "$1")" == "file" ]]; then unbuffer "$@" # Else, e.g., ls isn't colorized else - "$@" # Can't unbuffer builtins (e.g., cd) + command "$@" # Can't unbuffer builtins (e.g., cd) fi # Remember these @@ -63,7 +65,7 @@ help50() { (exit $status) # Try to get help - local help=$( . "${HELPERS}/${command}.sh" <<< "$output" ) + local help=$( . "${HELPERS}/${command}" <<< "$output" ) if [[ -n "$help" ]]; then echo "$help" > "$HELP" elif [[ $status -ne 0 ]]; then @@ -85,8 +87,4 @@ _help50() { fi } -_help50() { - echo 222 -} - export PROMPT_COMMAND=_help50 diff --git a/opt/cs50/lib/help50/cd b/opt/cs50/lib/help50/cd new file mode 100755 index 0000000..a9bf588 --- /dev/null +++ b/opt/cs50/lib/help50/cd @@ -0,0 +1 @@ +#!/bin/bash diff --git a/opt/cs50/lib/help50/cd.sh b/opt/cs50/lib/help50/cd.sh deleted file mode 100644 index e69de29..0000000 diff --git a/opt/cs50/lib/help50/ls b/opt/cs50/lib/help50/ls new file mode 100755 index 0000000..a9bf588 --- /dev/null +++ b/opt/cs50/lib/help50/ls @@ -0,0 +1 @@ +#!/bin/bash diff --git a/opt/cs50/lib/help50/ls.sh b/opt/cs50/lib/help50/ls.sh deleted file mode 100644 index e69de29..0000000 diff --git a/opt/cs50/lib/help50/make.sh b/opt/cs50/lib/help50/make old mode 100644 new mode 100755 similarity index 98% rename from opt/cs50/lib/help50/make.sh rename to opt/cs50/lib/help50/make index 136c8f0..c6464be --- a/opt/cs50/lib/help50/make.sh +++ b/opt/cs50/lib/help50/make @@ -1,3 +1,5 @@ +#!/bin/bash + output=$(cat) regex="make: Nothing to be done for '(.*)'" From 5470fcf80b6c424c2ef6b9fd7dc9a14764a37701 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 5 Jan 2024 20:09:13 -0500 Subject: [PATCH 04/76] removed support for exit statuses, supporting any executable helper --- etc/profile.d/help50.sh | 7 +------ opt/cs50/lib/help50/make | 6 +++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 3a023f5..7647bd8 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -6,8 +6,6 @@ FILE="/tmp/help50.$$" # Use PID to support multiple terminals HELP="${FILE}.help" OUTPUT="${FILE}.output" -alias cd="HOME=/tmp cd" - # Supported helpers for helper in "$HELPERS"/*; do name=$(basename "$helper") @@ -61,11 +59,8 @@ help50() { # Remove tee'd output rm --force "$file" - # Restore $? to that of original command - (exit $status) - # Try to get help - local help=$( . "${HELPERS}/${command}" <<< "$output" ) + local help=$("${HELPERS}/${command}" "$@" <<< "$output") if [[ -n "$help" ]]; then echo "$help" > "$HELP" elif [[ $status -ne 0 ]]; then diff --git a/opt/cs50/lib/help50/make b/opt/cs50/lib/help50/make index c6464be..9cbb813 100755 --- a/opt/cs50/lib/help50/make +++ b/opt/cs50/lib/help50/make @@ -8,7 +8,7 @@ if [[ "$output" =~ $regex ]]; then # If target is a directory if [[ -d "$1" ]]; then echo "Cannot run ${bold}make${normal} on a directory. Did you mean to ${bold}cd ${1}${normal} first?" - return + exit fi # If target ends with .c @@ -16,7 +16,7 @@ if [[ "$output" =~ $regex ]]; then base="${1%.c}" if [[ -n "$base" && ! -d "$base" ]]; then echo "Did you mean to ${bold}make ${base}${normal}?" - return + exit fi fi @@ -39,6 +39,6 @@ if [[ "$output" =~ $regex ]]; then else echo fi - return + exit fi fi From c4b6fbefba45daebfaa87e768899c3420ff5dcf5 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 5 Jan 2024 20:43:01 -0500 Subject: [PATCH 05/76] added PIPESTATUS for helpers --- etc/profile.d/help50.sh | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 7647bd8..2d5e8dd 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -18,12 +18,6 @@ normal=$(tput sgr0) help50() { - # Check for helper - if [[ $# -gt 0 && ! -f "${HELPERS}/${1}" ]]; then - echo "Sorry, ${bold}help50${normal} does not yet know how to help with this!" - return 1 - fi - # Duplicate file descriptors exec 3>&1 4>&2 @@ -60,7 +54,10 @@ help50() { rm --force "$file" # Try to get help - local help=$("${HELPERS}/${command}" "$@" <<< "$output") + local helper="${HELPERS}/${command}" + if [[ -f "$helper" && -x "$helper" ]]; then + local help=$(PIPESTATUS=($status) "$helper" "$@" <<< "$output") + fi if [[ -n "$help" ]]; then echo "$help" > "$HELP" elif [[ $status -ne 0 ]]; then From 8ee1e52500efcf630802e053c8c01cb97e48b123 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 5 Jan 2024 21:05:28 -0500 Subject: [PATCH 06/76] removed PIPESTATUS --- etc/profile.d/help50.sh | 2 +- foo.sh | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100755 foo.sh diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 2d5e8dd..1ee222d 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -56,7 +56,7 @@ help50() { # Try to get help local helper="${HELPERS}/${command}" if [[ -f "$helper" && -x "$helper" ]]; then - local help=$(PIPESTATUS=($status) "$helper" "$@" <<< "$output") + local help=$("$helper" "$@" <<< "$output") fi if [[ -n "$help" ]]; then echo "$help" > "$HELP" diff --git a/foo.sh b/foo.sh new file mode 100755 index 0000000..f96cda6 --- /dev/null +++ b/foo.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +x=(a b c) +y=foo +export x +export y +env From b003947a79048988fedfb60c670903ead63ae408 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 5 Jan 2024 21:05:39 -0500 Subject: [PATCH 07/76] removed test file --- foo.sh | 7 ------- 1 file changed, 7 deletions(-) delete mode 100755 foo.sh diff --git a/foo.sh b/foo.sh deleted file mode 100755 index f96cda6..0000000 --- a/foo.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -x=(a b c) -y=foo -export x -export y -env From 9279b1dba4564a8da0c28cd81d600cb2212e7331 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 5 Jan 2024 22:00:55 -0500 Subject: [PATCH 08/76] added _helpful, _helpless --- etc/profile.d/help50.sh | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 1ee222d..88bf7c0 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -3,8 +3,8 @@ HELPERS="/opt/cs50/lib/help50" # Temporary files FILE="/tmp/help50.$$" # Use PID to support multiple terminals -HELP="${FILE}.help" -OUTPUT="${FILE}.output" +HELPFUL="${FILE}.help" +HELPLESS="${FILE}.output" # Supported helpers for helper in "$HELPERS"/*; do @@ -12,10 +12,6 @@ for helper in "$HELPERS"/*; do eval "function ${name}() { help50 "$name" \"\$@\"; }" done -# Formatting -bold=$(tput bold) -normal=$(tput sgr0) - help50() { # Duplicate file descriptors @@ -58,25 +54,28 @@ help50() { if [[ -f "$helper" && -x "$helper" ]]; then local help=$("$helper" "$@" <<< "$output") fi - if [[ -n "$help" ]]; then - echo "$help" > "$HELP" - elif [[ $status -ne 0 ]]; then - echo "$output" > "$OUTPUT" + if [[ -n "$help" ]]; then # If helpful + echo "$help" > "$HELPFUL" + elif [[ $status -ne 0 ]]; then # If helpless + echo "$output" > "$HELPLESS" fi } _help50() { - #history -a - if [[ -f "$HELP" ]]; then - echo -n "🦆 " - cat "$HELP" - rm --force "$HELP" - elif [[ -f "$OUTPUT" ]]; then - echo non-zero but no helper - rm --force "$OUTPUT" - else - echo zero + if [[ -f "$HELPFUL" ]]; then + _helpful "$HELPFUL" + elif [[ -f "$HELPLESS" ]]; then + _helpless "$HELPLESS" fi + rm --force "$HELPFUL" "$HELPLESS" } -export PROMPT_COMMAND=_help50 +_helpful() { + echo -n "🦆 " + # https://www.gnu.org/software/termutils/manual/termutils-2.0/html_chapter/tput_1.html#SEC8 + cat "$1" | sed "s/\`\([^\`]*\)\`/$(tput smso)\1$(tput rmso)/g" +} + +_helpless() { :; } + +export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }_help50" From 26e431d940bda3dd4098661fbc058392cfee1071 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 5 Jan 2024 22:01:01 -0500 Subject: [PATCH 09/76] removed, updated wrappers --- opt/cs50/bin/cp | 3 --- opt/cs50/bin/curl | 3 --- opt/cs50/lib/help50/make | 8 ++++---- 3 files changed, 4 insertions(+), 10 deletions(-) delete mode 100755 opt/cs50/bin/cp delete mode 100755 opt/cs50/bin/curl diff --git a/opt/cs50/bin/cp b/opt/cs50/bin/cp deleted file mode 100755 index 9a2ab78..0000000 --- a/opt/cs50/bin/cp +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -cp --interactive "$@" diff --git a/opt/cs50/bin/curl b/opt/cs50/bin/curl deleted file mode 100755 index 0f6171c..0000000 --- a/opt/cs50/bin/curl +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -curl --http2 "$@" diff --git a/opt/cs50/lib/help50/make b/opt/cs50/lib/help50/make index 9cbb813..2b89551 100755 --- a/opt/cs50/lib/help50/make +++ b/opt/cs50/lib/help50/make @@ -7,7 +7,7 @@ if [[ "$output" =~ $regex ]]; then # If target is a directory if [[ -d "$1" ]]; then - echo "Cannot run ${bold}make${normal} on a directory. Did you mean to ${bold}cd ${1}${normal} first?" + echo "Cannot run \`make\` on a directory. Did you mean to \`cd ${1}\` first?" exit fi @@ -15,7 +15,7 @@ if [[ "$output" =~ $regex ]]; then if [[ "$1" == *?.c ]]; then base="${1%.c}" if [[ -n "$base" && ! -d "$base" ]]; then - echo "Did you mean to ${bold}make ${base}${normal}?" + echo "Did you mean to \`make ${base}\`?" exit fi fi @@ -32,10 +32,10 @@ if [[ "$output" =~ $regex ]]; then # Search recursively for .c file paths=$(find * -name "$c" 2> /dev/null) lines=$(echo "$paths" | grep -c .) - echo -n "There isn't a file called ${bold}${c}${normal} in your current directory." + echo -n "There isn't a file called \`${c}\` in your current directory." if [[ "$lines" -eq 1 ]]; then # If unambiguous d=$(dirname "$paths") - echo " Did you mean to ${bold}cd ${d}${normal} first?" + echo " Did you mean to \`cd ${d}\` first?" else echo fi From a0783ce10bcd0a8c7e0faf276ce7d7aaebcf9b7b Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 5 Jan 2024 22:15:14 -0500 Subject: [PATCH 10/76] updated formatting --- opt/cs50/bin/http-server | 8 ++++---- opt/cs50/bin/sqlite3 | 10 +++++----- opt/cs50/bin/valgrind | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/opt/cs50/bin/http-server b/opt/cs50/bin/http-server index a0f1c26..120cd75 100755 --- a/opt/cs50/bin/http-server +++ b/opt/cs50/bin/http-server @@ -10,12 +10,12 @@ options="--no-dotfiles" t="-t0" # Formatting -bold=$(tput bold) -normal=$(tput sgr0) +smso=$(tput smso) +rmso=$(tput sgr0) # Check for app.py or wsgi.py if [[ -f app.py ]] || [[ -f wsgi.py ]]; then - read -p "Are you sure you want to run ${bold}http-server${normal} and not ${bold}flask${normal}? [y/N] " -r + read -p "Are you sure you want to run ${smso}http-server${rmso} and not ${smso}flask${rmso}? [y/N] " -r if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then exit 1 fi @@ -23,7 +23,7 @@ fi # Check for path if [[ $# -eq 1 ]] && [[ $1 != -* ]] && [[ ! $1 =~ ^\./?$ ]]; then - read -p "Are you sure you want to serve ${bold}${1}${normal} and not your current directory? [y/N] " -r + read -p "Are you sure you want to serve ${smso}${1}${rmso} and not your current directory? [y/N] " -r if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then exit 1 fi diff --git a/opt/cs50/bin/sqlite3 b/opt/cs50/bin/sqlite3 index 3c1c0e7..1aa8f93 100755 --- a/opt/cs50/bin/sqlite3 +++ b/opt/cs50/bin/sqlite3 @@ -1,8 +1,8 @@ #!/bin/bash # Formatting -bold=$(tput bold) -normal=$(tput sgr0) +smso=$(tput smso) +rmso=$(tput sgr0) # If data is coming from stdin (pipe or redirection) if [[ -p /dev/stdin || ! -t 0 ]]; then @@ -12,7 +12,7 @@ fi # If no command-line argument if [[ $# -eq 0 ]]; then - read -p "Are you sure you want to run ${bold}sqlite3${normal} without a command-line argument (e.g., the filename of a database)? [y/N] " -r + read -p "Are you sure you want to run ${smso}sqlite3${rmso} without a command-line argument (e.g., the filename of a database)? [y/N] " -r if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then exit 1 fi @@ -21,12 +21,12 @@ if [[ $# -eq 0 ]]; then elif [[ $# -eq 1 ]] && [[ ! "$1" =~ ^- ]]; then if [[ ! -f "$1" ]]; then if [[ ! "$1" =~ \.db$ ]]; then - read -p "Are you sure you want to create ${bold}$1${normal}? SQLite filenames usually end in ${bold}.db${normal}. [y/N] " -r + read -p "Are you sure you want to create ${smso}$1${rmso}? SQLite filenames usually end in ${smso}.db${rmso}. [y/N] " -r if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then exit 1 fi else - read -p "Are you sure you want to create ${bold}$1${normal}? [y/N] " -r + read -p "Are you sure you want to create ${smso}$1${rmso}? [y/N] " -r if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then exit 1 fi diff --git a/opt/cs50/bin/valgrind b/opt/cs50/bin/valgrind index 60a6429..2ed1e53 100755 --- a/opt/cs50/bin/valgrind +++ b/opt/cs50/bin/valgrind @@ -1,12 +1,12 @@ #!/bin/bash # Formatting -bold=$(tput bold) -normal=$(tput sgr0) +smso=$(tput smso) +rmso=$(tput sgr0) # If run on Python program if [[ "$1" == "python" || "$1" == *.py ]]; then - echo "Afraid ${bold}valgrind${normal} does not support Python programs!" + echo "Afraid ${smso}valgrind${rmso} does not support Python programs!" exit 1 fi From cadd89002b0b1c659cb2a8d92b91d0af0fd53053 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 5 Jan 2024 22:38:26 -0500 Subject: [PATCH 11/76] added _help function to standardize formatting --- etc/profile.d/help50.sh | 3 +-- opt/cs50/bin/_help | 13 +++++++++++++ opt/cs50/bin/http-server | 8 ++------ opt/cs50/bin/sqlite3 | 10 +++------- opt/cs50/bin/valgrind | 6 +----- 5 files changed, 20 insertions(+), 20 deletions(-) create mode 100755 opt/cs50/bin/_help diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 88bf7c0..7a4b37e 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -72,8 +72,7 @@ _help50() { _helpful() { echo -n "🦆 " - # https://www.gnu.org/software/termutils/manual/termutils-2.0/html_chapter/tput_1.html#SEC8 - cat "$1" | sed "s/\`\([^\`]*\)\`/$(tput smso)\1$(tput rmso)/g" + cat "$1" | _help } _helpless() { :; } diff --git a/opt/cs50/bin/_help b/opt/cs50/bin/_help new file mode 100755 index 0000000..fc7734b --- /dev/null +++ b/opt/cs50/bin/_help @@ -0,0 +1,13 @@ +#!/bin/bash + +# If command-line arguments +if [[ -t 0 ]]; then + input="$*" + +# If standard input +else + input=$(cat) +fi + +# https://www.gnu.org/software/termutils/manual/termutils-2.0/html_chapter/tput_1.html#SEC8 +echo "$input" | sed "s/\`\([^\`]*\)\`/$(tput smso)\1$(tput rmso)/g" diff --git a/opt/cs50/bin/http-server b/opt/cs50/bin/http-server index 120cd75..dcb9369 100755 --- a/opt/cs50/bin/http-server +++ b/opt/cs50/bin/http-server @@ -9,13 +9,9 @@ port="-p 8080" options="--no-dotfiles" t="-t0" -# Formatting -smso=$(tput smso) -rmso=$(tput sgr0) - # Check for app.py or wsgi.py if [[ -f app.py ]] || [[ -f wsgi.py ]]; then - read -p "Are you sure you want to run ${smso}http-server${rmso} and not ${smso}flask${rmso}? [y/N] " -r + read -p "$(_help "Are you sure you want to run \`http-server\` and not \`flask\`? [y/N] ")" -r if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then exit 1 fi @@ -23,7 +19,7 @@ fi # Check for path if [[ $# -eq 1 ]] && [[ $1 != -* ]] && [[ ! $1 =~ ^\./?$ ]]; then - read -p "Are you sure you want to serve ${smso}${1}${rmso} and not your current directory? [y/N] " -r + read -p "$(_help "Are you sure you want to serve \`${1}\` and not your current directory? [y/N] ")" -r if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then exit 1 fi diff --git a/opt/cs50/bin/sqlite3 b/opt/cs50/bin/sqlite3 index 1aa8f93..d59bcd5 100755 --- a/opt/cs50/bin/sqlite3 +++ b/opt/cs50/bin/sqlite3 @@ -1,9 +1,5 @@ #!/bin/bash -# Formatting -smso=$(tput smso) -rmso=$(tput sgr0) - # If data is coming from stdin (pipe or redirection) if [[ -p /dev/stdin || ! -t 0 ]]; then /usr/local/bin/sqlite3 -nullvalue NULL -table "$@" < /dev/stdin @@ -12,7 +8,7 @@ fi # If no command-line argument if [[ $# -eq 0 ]]; then - read -p "Are you sure you want to run ${smso}sqlite3${rmso} without a command-line argument (e.g., the filename of a database)? [y/N] " -r + read -p "$(_help "Are you sure you want to run \`sqlite3\` without a command-line argument (e.g., the filename of a database)? [y/N] ")" -r if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then exit 1 fi @@ -21,12 +17,12 @@ if [[ $# -eq 0 ]]; then elif [[ $# -eq 1 ]] && [[ ! "$1" =~ ^- ]]; then if [[ ! -f "$1" ]]; then if [[ ! "$1" =~ \.db$ ]]; then - read -p "Are you sure you want to create ${smso}$1${rmso}? SQLite filenames usually end in ${smso}.db${rmso}. [y/N] " -r + read -p "$(_help "Are you sure you want to create \`$1\`? SQLite filenames usually end in \`.db\`. [y/N] ")" -r if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then exit 1 fi else - read -p "Are you sure you want to create ${smso}$1${rmso}? [y/N] " -r + read -p "$(_help "Are you sure you want to create \`$1\`? [y/N] ")" -r if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then exit 1 fi diff --git a/opt/cs50/bin/valgrind b/opt/cs50/bin/valgrind index 2ed1e53..dd19aa8 100755 --- a/opt/cs50/bin/valgrind +++ b/opt/cs50/bin/valgrind @@ -1,12 +1,8 @@ #!/bin/bash -# Formatting -smso=$(tput smso) -rmso=$(tput sgr0) - # If run on Python program if [[ "$1" == "python" || "$1" == *.py ]]; then - echo "Afraid ${smso}valgrind${rmso} does not support Python programs!" + echo "$(_help "Afraid \`valgrind\` does not support Python programs!")" exit 1 fi From 3041bf96b7ed0c566444ab68b2661444a3d3eeb7 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 5 Jan 2024 22:48:22 -0500 Subject: [PATCH 12/76] added on/off --- etc/profile.d/help50.sh | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 7a4b37e..3605fad 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -62,10 +62,12 @@ help50() { } _help50() { - if [[ -f "$HELPFUL" ]]; then - _helpful "$HELPFUL" - elif [[ -f "$HELPLESS" ]]; then - _helpless "$HELPLESS" + if [[ "$RUBBERDUCKING" != "0" ]]; then + if [[ -f "$HELPFUL" ]]; then + _helpful "$HELPFUL" + elif [[ -f "$HELPLESS" ]]; then + _helpless "$HELPLESS" + fi fi rm --force "$HELPFUL" "$HELPLESS" } @@ -77,4 +79,19 @@ _helpful() { _helpless() { :; } +duck() { + if [[ "$1" == "off" ]]; then + export RUBBERDUCKING=0 + elif [[ "$1" == "on" ]]; then + unset RUBBERDUCKING + elif [[ "$1" == "status" ]]; then + if [[ "$RUBBERDUCKING" == "0" ]]; then + echo "off" + else + echo "on" + fi + fi +} + export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }_help50" +duck on From a3c55b3c6425300740004974e700e9e7df826e74 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Tue, 9 Jan 2024 08:26:42 -0500 Subject: [PATCH 13/76] added icon to _help --- opt/cs50/bin/_help | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opt/cs50/bin/_help b/opt/cs50/bin/_help index fc7734b..7d6ef4f 100755 --- a/opt/cs50/bin/_help +++ b/opt/cs50/bin/_help @@ -10,4 +10,4 @@ else fi # https://www.gnu.org/software/termutils/manual/termutils-2.0/html_chapter/tput_1.html#SEC8 -echo "$input" | sed "s/\`\([^\`]*\)\`/$(tput smso)\1$(tput rmso)/g" +echo "⚠️ $input" | sed "s/\`\([^\`]*\)\`/$(tput smso)\1$(tput rmso)/g" From 7f315ae7500dbd7c6096531d401555432d6b4403 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 17 Jan 2024 13:39:15 -0500 Subject: [PATCH 14/76] WIP --- etc/profile.d/help50.sh | 13 +++++++++---- opt/cs50/bin/{_help => _ansi} | 2 +- opt/cs50/bin/_sure | 14 ++++++++++++++ opt/cs50/bin/http-server | 6 ++---- opt/cs50/bin/sqlite3 | 9 +++------ opt/cs50/lib/help50/bash | 13 +++++++++++++ typescript | 13 +++++++++++++ 7 files changed, 55 insertions(+), 15 deletions(-) rename opt/cs50/bin/{_help => _ansi} (73%) create mode 100755 opt/cs50/bin/_sure create mode 100755 opt/cs50/lib/help50/bash create mode 100644 typescript diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 3605fad..92885c8 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -1,6 +1,11 @@ # Directory with helpers HELPERS="/opt/cs50/lib/help50" +# Disable yes, lest students type it at prompt +if command -v yes &> /dev/null; then + alias yes=":" +fi + # Temporary files FILE="/tmp/help50.$$" # Use PID to support multiple terminals HELPFUL="${FILE}.help" @@ -51,6 +56,7 @@ help50() { # Try to get help local helper="${HELPERS}/${command}" + echo "HELPER: $helper" if [[ -f "$helper" && -x "$helper" ]]; then local help=$("$helper" "$@" <<< "$output") fi @@ -64,17 +70,16 @@ help50() { _help50() { if [[ "$RUBBERDUCKING" != "0" ]]; then if [[ -f "$HELPFUL" ]]; then - _helpful "$HELPFUL" + _helpful "$(cat "$HELPFUL")" elif [[ -f "$HELPLESS" ]]; then - _helpless "$HELPLESS" + _helpless "$(cat "$HELPLESS")" fi fi rm --force "$HELPFUL" "$HELPLESS" } _helpful() { - echo -n "🦆 " - cat "$1" | _help + echo "$1" } _helpless() { :; } diff --git a/opt/cs50/bin/_help b/opt/cs50/bin/_ansi similarity index 73% rename from opt/cs50/bin/_help rename to opt/cs50/bin/_ansi index 7d6ef4f..fc7734b 100755 --- a/opt/cs50/bin/_help +++ b/opt/cs50/bin/_ansi @@ -10,4 +10,4 @@ else fi # https://www.gnu.org/software/termutils/manual/termutils-2.0/html_chapter/tput_1.html#SEC8 -echo "⚠️ $input" | sed "s/\`\([^\`]*\)\`/$(tput smso)\1$(tput rmso)/g" +echo "$input" | sed "s/\`\([^\`]*\)\`/$(tput smso)\1$(tput rmso)/g" diff --git a/opt/cs50/bin/_sure b/opt/cs50/bin/_sure new file mode 100755 index 0000000..74f688a --- /dev/null +++ b/opt/cs50/bin/_sure @@ -0,0 +1,14 @@ +#!/bin/bash + +if [[ $# -ne 1 ]]; then + exit 1 +fi +prompt=$(echo "$1" | _ansi) +while true; do + read -p "$prompt [y/N] " -r + if [[ "${REPLY,,}" =~ ^(y|yes)$ ]]; then + exit 0 + elif [[ "${REPLY,,}" =~ ^(n|no)$ ]]; then + exit 1 + fi +done diff --git a/opt/cs50/bin/http-server b/opt/cs50/bin/http-server index dcb9369..7bbcd9e 100755 --- a/opt/cs50/bin/http-server +++ b/opt/cs50/bin/http-server @@ -11,16 +11,14 @@ t="-t0" # Check for app.py or wsgi.py if [[ -f app.py ]] || [[ -f wsgi.py ]]; then - read -p "$(_help "Are you sure you want to run \`http-server\` and not \`flask\`? [y/N] ")" -r - if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then + if ! _sure "Are you sure you want to run \`http-server\` and not \`flask\`?"; then exit 1 fi fi # Check for path if [[ $# -eq 1 ]] && [[ $1 != -* ]] && [[ ! $1 =~ ^\./?$ ]]; then - read -p "$(_help "Are you sure you want to serve \`${1}\` and not your current directory? [y/N] ")" -r - if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then + if ! _sure "Are you sure you want to serve \`${1}\` and not your current directory?"; then exit 1 fi fi diff --git a/opt/cs50/bin/sqlite3 b/opt/cs50/bin/sqlite3 index d59bcd5..80557d8 100755 --- a/opt/cs50/bin/sqlite3 +++ b/opt/cs50/bin/sqlite3 @@ -8,8 +8,7 @@ fi # If no command-line argument if [[ $# -eq 0 ]]; then - read -p "$(_help "Are you sure you want to run \`sqlite3\` without a command-line argument (e.g., the filename of a database)? [y/N] ")" -r - if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then + if ! _sure "Are you sure you want to run \`sqlite3\` without a command-line argument (e.g., the filename of a database)?"; then exit 1 fi @@ -17,13 +16,11 @@ if [[ $# -eq 0 ]]; then elif [[ $# -eq 1 ]] && [[ ! "$1" =~ ^- ]]; then if [[ ! -f "$1" ]]; then if [[ ! "$1" =~ \.db$ ]]; then - read -p "$(_help "Are you sure you want to create \`$1\`? SQLite filenames usually end in \`.db\`. [y/N] ")" -r - if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then + if ! _sure "Are you sure you want to create \`$1\`? SQLite filenames usually end in \`.db\`."; then exit 1 fi else - read -p "$(_help "Are you sure you want to create \`$1\`? [y/N] ")" -r - if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then + if ! _sure "Are you sure you want to create \`$1\`?"; then exit 1 fi fi diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash new file mode 100755 index 0000000..f34adec --- /dev/null +++ b/opt/cs50/lib/help50/bash @@ -0,0 +1,13 @@ +#!/bin/bash + +output=$(cat) + +regex="bash: (.*).py: command not found" +if [[ "$output" =~ $regex ]]; then + + # If target is a directory + if [[ -f "$1" ]]; then + echo "Did you mean to run \`python ${1}`?" + exit + fi +fi diff --git a/typescript b/typescript new file mode 100644 index 0000000..e196aef --- /dev/null +++ b/typescript @@ -0,0 +1,13 @@ +Script started on 2024-01-09 14:43:54+00:00 [TERM="xterm" TTY="/dev/pts/0" COLUMNS="130" LINES="33"] +$ vi +[?1049h[>4;2m[?1h=[?2004h[?1004h[?12h[?12l▽ Pzz\[0%m [>c]10;?]11;? [No Name]  [?25l~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~  [No Name] CWD: /home/ubuntu Line: 0 VIM - Vi IMprovedversion 8.2.2121by Bram Moolenaar et al.Modified by team+vim@tracker.debian.orgVim is open source and freely distributableSponsor Vim development!type :help sponsor for information type :q to exit type :help or  for on-line helptype :help version8 for version info[?25h[?25l::[?25hq [?25l +[?2004l[>4;m[?1004l[?2004l[?1l>[?25h[>4;m[?1049l$ ls +Dockerfile etc foo.py index.js.patch LICENSE Makefile opt shell.c.patch tests tmp typescript +$ scrip^C +$ vi +[?1049h[>4;2m[?1h=[?2004h[?1004h[?12h[?12l▽ Pzz\[0%m [>c]10;?]11;? [No Name]  [?25l~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~  [No Name] CWD: /home/ubuntu Line: 0 VIM - Vi IMprovedversion 8.2.2121by Bram Moolenaar et al.Modified by team+vim@tracker.debian.orgVim is open source and freely distributableHelp poor children in Uganda!type :help iccf for information type :q to exit type :help or  for on-line helptype :help version8 for version info[?25h[?25l^M [?25h[?25l^M [?25h[?25la -- INSERT --[?25h+N o[No Name] [?25l          [+] CWD: /home/ubuntu Line: 2[?25h[?25l3 [?25h[?25l4 [?25h[?25l5 [?25h[?25l^[ [?25h[?25l::[?25hwq [?25lE32: No file name[?25h[?25ll [?25h[?25ls cl -- INSERT --[?25h[?25l^[ [?25h[?25l::[?25hwq! [?25lE32: No file name[?25h[?25l::[?25hw foo [?25l"foo" [New] 5L, 5B writtenfooN o ~/foo CWD: /home/ubuntu Line: 5 [?25h[?25l::[?25hq [?25l +[?2004l[>4;m[?1004l[?2004l[?1l>[?25h[>4;m[?1049l$ ls +Dockerfile etc foo foo.py index.js.patch LICENSE Makefile opt shell.c.patch tests tmp typescript +$ l  + +Script done on 2024-01-09 14:44:16+00:00 [COMMAND_EXIT_CODE="0"] From 5c70d230fa26a022e60a7a3b63cb4140aa0005c2 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 17 Jan 2024 13:40:41 -0500 Subject: [PATCH 15/76] WIP --- typescript | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 typescript diff --git a/typescript b/typescript deleted file mode 100644 index e196aef..0000000 --- a/typescript +++ /dev/null @@ -1,13 +0,0 @@ -Script started on 2024-01-09 14:43:54+00:00 [TERM="xterm" TTY="/dev/pts/0" COLUMNS="130" LINES="33"] -$ vi -[?1049h[>4;2m[?1h=[?2004h[?1004h[?12h[?12l▽ Pzz\[0%m [>c]10;?]11;? [No Name]  [?25l~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~  [No Name] CWD: /home/ubuntu Line: 0 VIM - Vi IMprovedversion 8.2.2121by Bram Moolenaar et al.Modified by team+vim@tracker.debian.orgVim is open source and freely distributableSponsor Vim development!type :help sponsor for information type :q to exit type :help or  for on-line helptype :help version8 for version info[?25h[?25l::[?25hq [?25l -[?2004l[>4;m[?1004l[?2004l[?1l>[?25h[>4;m[?1049l$ ls -Dockerfile etc foo.py index.js.patch LICENSE Makefile opt shell.c.patch tests tmp typescript -$ scrip^C -$ vi -[?1049h[>4;2m[?1h=[?2004h[?1004h[?12h[?12l▽ Pzz\[0%m [>c]10;?]11;? [No Name]  [?25l~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~  [No Name] CWD: /home/ubuntu Line: 0 VIM - Vi IMprovedversion 8.2.2121by Bram Moolenaar et al.Modified by team+vim@tracker.debian.orgVim is open source and freely distributableHelp poor children in Uganda!type :help iccf for information type :q to exit type :help or  for on-line helptype :help version8 for version info[?25h[?25l^M [?25h[?25l^M [?25h[?25la -- INSERT --[?25h+N o[No Name] [?25l          [+] CWD: /home/ubuntu Line: 2[?25h[?25l3 [?25h[?25l4 [?25h[?25l5 [?25h[?25l^[ [?25h[?25l::[?25hwq [?25lE32: No file name[?25h[?25ll [?25h[?25ls cl -- INSERT --[?25h[?25l^[ [?25h[?25l::[?25hwq! [?25lE32: No file name[?25h[?25l::[?25hw foo [?25l"foo" [New] 5L, 5B writtenfooN o ~/foo CWD: /home/ubuntu Line: 5 [?25h[?25l::[?25hq [?25l -[?2004l[>4;m[?1004l[?2004l[?1l>[?25h[>4;m[?1049l$ ls -Dockerfile etc foo foo.py index.js.patch LICENSE Makefile opt shell.c.patch tests tmp typescript -$ l  - -Script done on 2024-01-09 14:44:16+00:00 [COMMAND_EXIT_CODE="0"] From 36c860c6d338c065696747a148ef8721891783fc Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 18 Jan 2024 13:42:53 -0500 Subject: [PATCH 16/76] WIP --- etc/profile.d/help50.sh | 157 ++++++++++++++++++------------------ etc/profile.d/help50.sh.old | 102 +++++++++++++++++++++++ opt/cs50/bin/make | 4 +- opt/cs50/lib/help50/bash | 4 +- opt/cs50/lib/help50/make | 8 +- 5 files changed, 189 insertions(+), 86 deletions(-) create mode 100644 etc/profile.d/help50.sh.old diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 92885c8..8eda6ed 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -1,102 +1,101 @@ # Directory with helpers HELPERS="/opt/cs50/lib/help50" -# Disable yes, lest students type it at prompt +# Disable yes, lest users type it at prompt if command -v yes &> /dev/null; then alias yes=":" fi -# Temporary files -FILE="/tmp/help50.$$" # Use PID to support multiple terminals -HELPFUL="${FILE}.help" -HELPLESS="${FILE}.output" +function _help50 () { -# Supported helpers -for helper in "$HELPERS"/*; do - name=$(basename "$helper") - eval "function ${name}() { help50 "$name" \"\$@\"; }" -done - -help50() { - - # Duplicate file descriptors - exec 3>&1 4>&2 - - # Redirect output to a file too - exec > >(tee -a "$FILE") 2>&1 - - # Execute command - if [[ "$(type -P -t "$1")" == "file" ]]; then - unbuffer "$@" # Else, e.g., ls isn't colorized - else - command "$@" # Can't unbuffer builtins (e.g., cd) - fi - - # Remember these + # Exit status of last command local status=$? - local command="$1" - - # Remove command from $@ - shift - # Get tee'd output - local output=$(cat "$FILE") + # Last command + local argv=$(fc -ln -1 | sed 's/^[[:space:]]*//') + # TODO: extract argv0 to determine if ./ command - # Remove any ANSI codes - output=$(echo "$output" | ansi2txt | col -b) + # If no typescript yet + if [[ -z "$SCRIPT" ]]; then - # Restore file descriptors - exec 1>&3 2>&4 + # Use this shell's PID as typescript's name, exporting so that subshells know script is already running + export SCRIPT="/tmp/help50.$$" - # Close file descriptors - exec 3>&- 4>&- + # Make a typescript of everything displayed in terminal (without using exec, which breaks sudo); + # --append avoids `bash: warning: command substitution: ignored null byte in input`; + # --quiet suppresses `Script started...` + script --append --command "bash --login" --flush --quiet "$SCRIPT" - # Remove tee'd output - rm --force "$file" + # Remove typescript before exiting this shell + rm --force "$SCRIPT" - # Try to get help - local helper="${HELPERS}/${command}" - echo "HELPER: $helper" - if [[ -f "$helper" && -x "$helper" ]]; then - local help=$("$helper" "$@" <<< "$output") + # Now exit this shell too + exit fi - if [[ -n "$help" ]]; then # If helpful - echo "$help" > "$HELPFUL" - elif [[ $status -ne 0 ]]; then # If helpless - echo "$output" > "$HELPLESS" - fi -} -_help50() { - if [[ "$RUBBERDUCKING" != "0" ]]; then - if [[ -f "$HELPFUL" ]]; then - _helpful "$(cat "$HELPFUL")" - elif [[ -f "$HELPLESS" ]]; then - _helpless "$(cat "$HELPLESS")" + # If last command erred + if [[ $status -ne 0 ]]; then + + # Read typescript from disk + local typescript=$(cat "$SCRIPT") + + # Remove script's own output (if this is user's first command) + typescript=$(echo "$typescript" | sed '1{/^Script started on .*/d}') + + # Remove any line continuations from command line + local lines="" + while IFS= read -r line || [[ -n "$line" ]]; do + if [[ -z $done && $line =~ \\$ ]]; then + lines+="${line%\\}" + else + lines+="$line"$'\n' + local done=1 + fi + done <<< "$typescript" + typescript="$lines" + + # Remove command line from typescript + typescript=$(echo "$typescript" | sed '1d') + + # Remove ANSI characters + typescript=$(echo "$typescript" | ansi2txt) + + # Remove control characters + # https://superuser.com/a/237154 + typescript=$(echo "$typescript" | col -bp) + + # Try to get help + for helper in "$HELPERS"/*; do + if [[ -f "$helper" && -x "$helper" ]]; then + local help=$("$helper" <<< "$typescript") + if [[ -n "$help" ]]; then + break + fi + fi + done + if [[ -n "$help" ]]; then # If helpful + _helpful "$help" + elif [[ $status -ne 0 ]]; then # If helpless + _helpless "$text" fi + + # TEMP + echo "TEXT: $typescript" >> "$SCRIPT.log" + echo "---" >> "$SCRIPT.log" fi - rm --force "$HELPFUL" "$HELPLESS" -} -_helpful() { - echo "$1" + # Truncate typescript + truncate -s 0 "$SCRIPT" } -_helpless() { :; } - -duck() { - if [[ "$1" == "off" ]]; then - export RUBBERDUCKING=0 - elif [[ "$1" == "on" ]]; then - unset RUBBERDUCKING - elif [[ "$1" == "status" ]]; then - if [[ "$RUBBERDUCKING" == "0" ]]; then - echo "off" - else - echo "on" - fi - fi -} +if ! type _helpful >/dev/null 2>&1; then + function _helpful() { + echo "$1" + } +fi + +if ! type _helpless >/dev/null 2>&1; then + function _helpless() { :; } +fi -export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }_help50" -duck on +export PROMPT_COMMAND="_help50${PROMPT_COMMAND:+; $PROMPT_COMMAND}" diff --git a/etc/profile.d/help50.sh.old b/etc/profile.d/help50.sh.old new file mode 100644 index 0000000..92885c8 --- /dev/null +++ b/etc/profile.d/help50.sh.old @@ -0,0 +1,102 @@ +# Directory with helpers +HELPERS="/opt/cs50/lib/help50" + +# Disable yes, lest students type it at prompt +if command -v yes &> /dev/null; then + alias yes=":" +fi + +# Temporary files +FILE="/tmp/help50.$$" # Use PID to support multiple terminals +HELPFUL="${FILE}.help" +HELPLESS="${FILE}.output" + +# Supported helpers +for helper in "$HELPERS"/*; do + name=$(basename "$helper") + eval "function ${name}() { help50 "$name" \"\$@\"; }" +done + +help50() { + + # Duplicate file descriptors + exec 3>&1 4>&2 + + # Redirect output to a file too + exec > >(tee -a "$FILE") 2>&1 + + # Execute command + if [[ "$(type -P -t "$1")" == "file" ]]; then + unbuffer "$@" # Else, e.g., ls isn't colorized + else + command "$@" # Can't unbuffer builtins (e.g., cd) + fi + + # Remember these + local status=$? + local command="$1" + + # Remove command from $@ + shift + + # Get tee'd output + local output=$(cat "$FILE") + + # Remove any ANSI codes + output=$(echo "$output" | ansi2txt | col -b) + + # Restore file descriptors + exec 1>&3 2>&4 + + # Close file descriptors + exec 3>&- 4>&- + + # Remove tee'd output + rm --force "$file" + + # Try to get help + local helper="${HELPERS}/${command}" + echo "HELPER: $helper" + if [[ -f "$helper" && -x "$helper" ]]; then + local help=$("$helper" "$@" <<< "$output") + fi + if [[ -n "$help" ]]; then # If helpful + echo "$help" > "$HELPFUL" + elif [[ $status -ne 0 ]]; then # If helpless + echo "$output" > "$HELPLESS" + fi +} + +_help50() { + if [[ "$RUBBERDUCKING" != "0" ]]; then + if [[ -f "$HELPFUL" ]]; then + _helpful "$(cat "$HELPFUL")" + elif [[ -f "$HELPLESS" ]]; then + _helpless "$(cat "$HELPLESS")" + fi + fi + rm --force "$HELPFUL" "$HELPLESS" +} + +_helpful() { + echo "$1" +} + +_helpless() { :; } + +duck() { + if [[ "$1" == "off" ]]; then + export RUBBERDUCKING=0 + elif [[ "$1" == "on" ]]; then + unset RUBBERDUCKING + elif [[ "$1" == "status" ]]; then + if [[ "$RUBBERDUCKING" == "0" ]]; then + echo "off" + else + echo "on" + fi + fi +} + +export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }_help50" +duck on diff --git a/opt/cs50/bin/make b/opt/cs50/bin/make index d4275e8..ca6cbd7 100755 --- a/opt/cs50/bin/make +++ b/opt/cs50/bin/make @@ -11,7 +11,9 @@ if [[ $# -eq 1 ]] && [[ "$1" != -* ]]; then # Don't suppress "Nothing to be done" with --silent /usr/bin/make "$1" - exit $? + + # Else make exits with 0 + exit 1 fi fi fi diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index f34adec..37f1a8a 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -6,8 +6,8 @@ regex="bash: (.*).py: command not found" if [[ "$output" =~ $regex ]]; then # If target is a directory - if [[ -f "$1" ]]; then - echo "Did you mean to run \`python ${1}`?" + if [[ -f "${BASH_REMATCH[1]}" ]]; then + echo "Did you mean to run \`python ${BASH_REMATCH[1]}\`?" exit fi fi diff --git a/opt/cs50/lib/help50/make b/opt/cs50/lib/help50/make index 2b89551..ee19a5f 100755 --- a/opt/cs50/lib/help50/make +++ b/opt/cs50/lib/help50/make @@ -6,14 +6,14 @@ regex="make: Nothing to be done for '(.*)'" if [[ "$output" =~ $regex ]]; then # If target is a directory - if [[ -d "$1" ]]; then + if [[ -d "${BASH_REMATCH[1]}" ]]; then echo "Cannot run \`make\` on a directory. Did you mean to \`cd ${1}\` first?" exit fi # If target ends with .c - if [[ "$1" == *?.c ]]; then - base="${1%.c}" + if [[ "${BASH_REMATCH[1]}" == *?.c ]]; then + base="${BASH_REMATCH[1]%.c}" if [[ -n "$base" && ! -d "$base" ]]; then echo "Did you mean to \`make ${base}\`?" exit @@ -26,7 +26,7 @@ regex="No rule to make target '(.*)'" if [[ "$output" =~ $regex ]]; then # If no .c file for target - local c="$1.c" + c="${BASH_REMATCH[1]}.c" if [[ ! -f "$c" ]]; then # Search recursively for .c file From 22a873bd9d827235e5ba1fedb26d1e9d56030084 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 18 Jan 2024 14:44:28 -0500 Subject: [PATCH 17/76] WIP --- etc/profile.d/help50.sh | 11 ++-- etc/profile.d/help50.sh.old | 102 ------------------------------------ opt/cs50/lib/help50/bash | 14 ++++- 3 files changed, 20 insertions(+), 107 deletions(-) delete mode 100644 etc/profile.d/help50.sh.old diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 8eda6ed..d9fd2d0 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -6,14 +6,19 @@ if command -v yes &> /dev/null; then alias yes=":" fi +# Ignore duplicates (but not commands that begin with spaces) +export HISTCONTROL="ignoredups" + function _help50 () { # Exit status of last command local status=$? - # Last command - local argv=$(fc -ln -1 | sed 's/^[[:space:]]*//') - # TODO: extract argv0 to determine if ./ command + # Append to history right away + history -a + + # Parse command + read -a argv <<< $(history 1 | cut -c 8-) # If no typescript yet if [[ -z "$SCRIPT" ]]; then diff --git a/etc/profile.d/help50.sh.old b/etc/profile.d/help50.sh.old deleted file mode 100644 index 92885c8..0000000 --- a/etc/profile.d/help50.sh.old +++ /dev/null @@ -1,102 +0,0 @@ -# Directory with helpers -HELPERS="/opt/cs50/lib/help50" - -# Disable yes, lest students type it at prompt -if command -v yes &> /dev/null; then - alias yes=":" -fi - -# Temporary files -FILE="/tmp/help50.$$" # Use PID to support multiple terminals -HELPFUL="${FILE}.help" -HELPLESS="${FILE}.output" - -# Supported helpers -for helper in "$HELPERS"/*; do - name=$(basename "$helper") - eval "function ${name}() { help50 "$name" \"\$@\"; }" -done - -help50() { - - # Duplicate file descriptors - exec 3>&1 4>&2 - - # Redirect output to a file too - exec > >(tee -a "$FILE") 2>&1 - - # Execute command - if [[ "$(type -P -t "$1")" == "file" ]]; then - unbuffer "$@" # Else, e.g., ls isn't colorized - else - command "$@" # Can't unbuffer builtins (e.g., cd) - fi - - # Remember these - local status=$? - local command="$1" - - # Remove command from $@ - shift - - # Get tee'd output - local output=$(cat "$FILE") - - # Remove any ANSI codes - output=$(echo "$output" | ansi2txt | col -b) - - # Restore file descriptors - exec 1>&3 2>&4 - - # Close file descriptors - exec 3>&- 4>&- - - # Remove tee'd output - rm --force "$file" - - # Try to get help - local helper="${HELPERS}/${command}" - echo "HELPER: $helper" - if [[ -f "$helper" && -x "$helper" ]]; then - local help=$("$helper" "$@" <<< "$output") - fi - if [[ -n "$help" ]]; then # If helpful - echo "$help" > "$HELPFUL" - elif [[ $status -ne 0 ]]; then # If helpless - echo "$output" > "$HELPLESS" - fi -} - -_help50() { - if [[ "$RUBBERDUCKING" != "0" ]]; then - if [[ -f "$HELPFUL" ]]; then - _helpful "$(cat "$HELPFUL")" - elif [[ -f "$HELPLESS" ]]; then - _helpless "$(cat "$HELPLESS")" - fi - fi - rm --force "$HELPFUL" "$HELPLESS" -} - -_helpful() { - echo "$1" -} - -_helpless() { :; } - -duck() { - if [[ "$1" == "off" ]]; then - export RUBBERDUCKING=0 - elif [[ "$1" == "on" ]]; then - unset RUBBERDUCKING - elif [[ "$1" == "status" ]]; then - if [[ "$RUBBERDUCKING" == "0" ]]; then - echo "off" - else - echo "on" - fi - fi -} - -export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }_help50" -duck on diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index 37f1a8a..de1198c 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -2,10 +2,20 @@ output=$(cat) -regex="bash: (.*).py: command not found" +regex="bash: (.*\.py): command not found" if [[ "$output" =~ $regex ]]; then - # If target is a directory + # If file exists + if [[ -f "${BASH_REMATCH[1]}" ]]; then + echo "Did you mean to run \`python ${BASH_REMATCH[1]}\`?" + exit + fi +fi + +regex="bash: (./.*\.py): Permission denied" +if [[ "$output" =~ $regex ]]; then + + # If file exists if [[ -f "${BASH_REMATCH[1]}" ]]; then echo "Did you mean to run \`python ${BASH_REMATCH[1]}\`?" exit From bbaa372e0ffba496cd795d0c31e11b8f2c4dad6d Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 18 Jan 2024 14:53:59 -0500 Subject: [PATCH 18/76] WIP --- etc/profile.d/help50.sh | 6 +----- opt/cs50/lib/help50/make | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index d9fd2d0..cbb310b 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -17,7 +17,7 @@ function _help50 () { # Append to history right away history -a - # Parse command + # Parse command line in case we want ${argv[0]} read -a argv <<< $(history 1 | cut -c 8-) # If no typescript yet @@ -83,10 +83,6 @@ function _help50 () { elif [[ $status -ne 0 ]]; then # If helpless _helpless "$text" fi - - # TEMP - echo "TEXT: $typescript" >> "$SCRIPT.log" - echo "---" >> "$SCRIPT.log" fi # Truncate typescript diff --git a/opt/cs50/lib/help50/make b/opt/cs50/lib/help50/make index ee19a5f..1e281ce 100755 --- a/opt/cs50/lib/help50/make +++ b/opt/cs50/lib/help50/make @@ -7,7 +7,7 @@ if [[ "$output" =~ $regex ]]; then # If target is a directory if [[ -d "${BASH_REMATCH[1]}" ]]; then - echo "Cannot run \`make\` on a directory. Did you mean to \`cd ${1}\` first?" + echo "Cannot run \`make\` on a directory. Did you mean to \`cd ${BASH_REMATCH[1]}\` first?" exit fi From b1b23603142477092863f6853d5a2e94dffa23b3 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 18 Jan 2024 15:18:27 -0500 Subject: [PATCH 19/76] checking for ctl-z --- etc/profile.d/help50.sh | 5 +++-- tests/foo/foo2/foo3/x.c | 0 tests/foo/qux | 0 tests/foo/y.c | 0 4 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 tests/foo/foo2/foo3/x.c create mode 100755 tests/foo/qux create mode 100644 tests/foo/y.c diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index cbb310b..377dc68 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -38,8 +38,9 @@ function _help50 () { exit fi - # If last command erred - if [[ $status -ne 0 ]]; then + # If last command erred (and not ctl-z) + # https://tldp.org/LDP/abs/html/exitcodes.html + if [[ $status -ne 0 && $status -ne 148 ]]; then # Read typescript from disk local typescript=$(cat "$SCRIPT") diff --git a/tests/foo/foo2/foo3/x.c b/tests/foo/foo2/foo3/x.c new file mode 100644 index 0000000..e69de29 diff --git a/tests/foo/qux b/tests/foo/qux new file mode 100755 index 0000000..e69de29 diff --git a/tests/foo/y.c b/tests/foo/y.c new file mode 100644 index 0000000..e69de29 From bd215bc8a900d5cec340e965a31543c1bf4a9134 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 18 Jan 2024 15:35:31 -0500 Subject: [PATCH 20/76] checking parent dirs --- opt/cs50/lib/help50/make | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/opt/cs50/lib/help50/make b/opt/cs50/lib/help50/make index 1e281ce..7cde67d 100755 --- a/opt/cs50/lib/help50/make +++ b/opt/cs50/lib/help50/make @@ -30,11 +30,13 @@ if [[ "$output" =~ $regex ]]; then if [[ ! -f "$c" ]]; then # Search recursively for .c file - paths=$(find * -name "$c" 2> /dev/null) + pushd "$(cd && pwd)" > /dev/null + paths=$(find $(pwd) -name "$c" 2> /dev/null) lines=$(echo "$paths" | grep -c .) + popd > /dev/null echo -n "There isn't a file called \`${c}\` in your current directory." if [[ "$lines" -eq 1 ]]; then # If unambiguous - d=$(dirname "$paths") + d=$(realpath --relative-to=. "$(dirname "$paths")") echo " Did you mean to \`cd ${d}\` first?" else echo From a69df221e1435cfb52089b8435cdec7f79c1083e Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 18 Jan 2024 16:34:38 -0500 Subject: [PATCH 21/76] added _find helper --- etc/profile.d/help50.sh | 23 +++++++++++++++++++++-- opt/cs50/lib/help50/make | 12 ++++-------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 377dc68..5bd4601 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -9,7 +9,7 @@ fi # Ignore duplicates (but not commands that begin with spaces) export HISTCONTROL="ignoredups" -function _help50 () { +function _help50() { # Exit status of last command local status=$? @@ -73,7 +73,7 @@ function _help50 () { # Try to get help for helper in "$HELPERS"/*; do if [[ -f "$helper" && -x "$helper" ]]; then - local help=$("$helper" <<< "$typescript") + local help=$(. $helper <<< "$typescript") if [[ -n "$help" ]]; then break fi @@ -90,6 +90,25 @@ function _help50 () { truncate -s 0 "$SCRIPT" } +function _find() { + + # In $1 is path to find + if [[ $# -ne 1 ]]; then + return + fi + + # Find absolute paths of any $1 relative to `cd` + pushd "$(cd && pwd)" > /dev/null + paths=$(find $(pwd) -name "$1" 2> /dev/null) + popd > /dev/null + + # Resolve absolute paths to relative paths + local line + while IFS= read -r path; do + realpath --relative-to=. "$(dirname "$path")" + done <<< "$paths" +} + if ! type _helpful >/dev/null 2>&1; then function _helpful() { echo "$1" diff --git a/opt/cs50/lib/help50/make b/opt/cs50/lib/help50/make index 7cde67d..599127d 100755 --- a/opt/cs50/lib/help50/make +++ b/opt/cs50/lib/help50/make @@ -30,17 +30,13 @@ if [[ "$output" =~ $regex ]]; then if [[ ! -f "$c" ]]; then # Search recursively for .c file - pushd "$(cd && pwd)" > /dev/null - paths=$(find $(pwd) -name "$c" 2> /dev/null) - lines=$(echo "$paths" | grep -c .) - popd > /dev/null + path=$(_find "$c") echo -n "There isn't a file called \`${c}\` in your current directory." - if [[ "$lines" -eq 1 ]]; then # If unambiguous - d=$(realpath --relative-to=. "$(dirname "$paths")") - echo " Did you mean to \`cd ${d}\` first?" + if [[ ! -z "$path" ]]; then + echo " Did you mean to \`cd $path\` first?" else echo fi - exit + exit fi fi From ebaec05f73538101e58c80e603e2fe575f89096e Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 18 Jan 2024 17:03:39 -0500 Subject: [PATCH 22/76] renamed helper --- etc/profile.d/help50.sh | 40 +++++++++++++++++++++++++++++++--------- opt/cs50/lib/help50/make | 4 ++-- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 5bd4601..2c0f1c8 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -90,23 +90,45 @@ function _help50() { truncate -s 0 "$SCRIPT" } -function _find() { +function _search() { # In $1 is path to find if [[ $# -ne 1 ]]; then return fi - # Find absolute paths of any $1 relative to `cd` - pushd "$(cd && pwd)" > /dev/null + # Find any $1 in descendants paths=$(find $(pwd) -name "$1" 2> /dev/null) - popd > /dev/null + if [[ -z "$paths" ]]; then + + # Find any $1 in ancestors + local dir="$(dirname "$(pwd)")" + while [[ "$dir" != "/" ]]; do + paths=$(find "$dir" -maxdepth 1 -name "$1") + if [[ -z "$paths" ]]; then + dir=$(dirname "$dir") + else + break + fi + done + if [[ -z "$paths" ]]; then - # Resolve absolute paths to relative paths - local line - while IFS= read -r path; do - realpath --relative-to=. "$(dirname "$path")" - done <<< "$paths" + # Find any $1 relative to `cd` + pushd "$(cd && pwd)" > /dev/null + paths=$(find $(pwd) -name "$1" 2> /dev/null) + popd > /dev/null + fi + fi + + # Count paths + count=$(echo "$paths" | grep -c .) + + # If just one + if [[ "$count" -eq 1 ]]; then + + # Resolve absolute path to relative path + realpath --relative-to=. "$(dirname "$paths")" + fi } if ! type _helpful >/dev/null 2>&1; then diff --git a/opt/cs50/lib/help50/make b/opt/cs50/lib/help50/make index 599127d..1cfc8ed 100755 --- a/opt/cs50/lib/help50/make +++ b/opt/cs50/lib/help50/make @@ -30,8 +30,8 @@ if [[ "$output" =~ $regex ]]; then if [[ ! -f "$c" ]]; then # Search recursively for .c file - path=$(_find "$c") - echo -n "There isn't a file called \`${c}\` in your current directory." + path=$(_search "$c") + echo -n "There isn't a file called \`${c}\` in your current directory. You are in \`$(basename "$PWD")\`." if [[ ! -z "$path" ]]; then echo " Did you mean to \`cd $path\` first?" else From bba678cc8b338975e60552339fdef89de30b3f00 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 18 Jan 2024 17:06:16 -0500 Subject: [PATCH 23/76] added helper --- opt/cs50/lib/help50/bash | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index de1198c..6c298b0 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -12,6 +12,11 @@ if [[ "$output" =~ $regex ]]; then fi fi +regex="bash: \./(.*): Is a directory" +if [[ "$output" =~ $regex ]]; then + echo "Cannot execute a directory. Did you mean to \`cd\` into \`${BASH_REMATCH[1]}\` instead?" +fi + regex="bash: (./.*\.py): Permission denied" if [[ "$output" =~ $regex ]]; then From 1a38952a63779bbd7dfc7f59f14ae3ba205b75b2 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 18 Jan 2024 17:17:05 -0500 Subject: [PATCH 24/76] added helper --- opt/cs50/lib/help50/bash | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index 6c298b0..ceaa0cd 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -26,3 +26,13 @@ if [[ "$output" =~ $regex ]]; then exit fi fi + +regex="bash: /.(.*): No such file or directory" +if [[ "$output" =~ $regex ]]; then + + # If file exists + if [[ -f "${BASH_REMATCH[1]}" ]]; then + echo "Did you mean to run \`./${BASH_REMATCH[1]}\`?" + exit + fi +fi From 501a59dc2084b63fa7890ea610e7b2091507d532 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 18 Jan 2024 21:33:04 -0500 Subject: [PATCH 25/76] added helpers --- opt/cs50/lib/help50/make | 14 +++++++------- opt/cs50/lib/help50/python | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) create mode 100755 opt/cs50/lib/help50/python diff --git a/opt/cs50/lib/help50/make b/opt/cs50/lib/help50/make index 1cfc8ed..7f9c913 100755 --- a/opt/cs50/lib/help50/make +++ b/opt/cs50/lib/help50/make @@ -22,18 +22,18 @@ if [[ "$output" =~ $regex ]]; then fi -regex="No rule to make target '(.*)'" +regex="make: \*\*\* No rule to make target '(.*)'" if [[ "$output" =~ $regex ]]; then # If no .c file for target - c="${BASH_REMATCH[1]}.c" - if [[ ! -f "$c" ]]; then + file="${BASH_REMATCH[1]}.c" + if [[ ! -f "$file" ]]; then # Search recursively for .c file - path=$(_search "$c") - echo -n "There isn't a file called \`${c}\` in your current directory. You are in \`$(basename "$PWD")\`." - if [[ ! -z "$path" ]]; then - echo " Did you mean to \`cd $path\` first?" + dir=$(_search "$file") + echo -n "There isn't a file called \`$file\` in your current directory." + if [[ ! -z "$dir" ]]; then + echo " Did you mean to \`cd $dir\` first?" else echo fi diff --git a/opt/cs50/lib/help50/python b/opt/cs50/lib/help50/python new file mode 100755 index 0000000..2474501 --- /dev/null +++ b/opt/cs50/lib/help50/python @@ -0,0 +1,38 @@ +#!/bin/bash + +output=$(cat) + +regex="AttributeError: module 'cs50' has no attribute '(get_(float|int|string)|SQL)'$" +if [[ "$output" =~ $regex ]]; then + if [[ -f cs50.py ]]; then + echo "You have a file called \`cs50.py\` that is \"shadowing\" CS50's own. Best to rename that file with \`mv\`." + exit + fi +fi + +regex='^\s*(import string\s*$|from\s+string\s+import\s+)' +if echo "$output" | grep -Pq "$regex"; then + if [[ -f string.py ]]; then + echo "You have a file called \`string.py\` that is \"shadowing\" Python's own. Best to rename that file with \`mv\`." + exit + fi +fi + +regex="python: can't open file '(.*\.py)': \[Errno 2\] No such file or directory" +if [[ "$output" =~ $regex ]]; then + + # Relative path from $PWD + path=$(realpath --relative-to=. "${BASH_REMATCH[1]}") + + # If .py is in $PWD + if [[ -n "$path" && "$path" == $(basename "$path") ]]; then + dir=$(_search "$path") + echo -n "There isn't a file called \`$path\` in your current directory." + if [[ ! -z "$dir" ]]; then + echo " Did you mean to \`cd $dir\` first?" + else + echo + fi + exit + fi +fi From 383f4a25a5a4c6066df0388479abf9299f217e01 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 31 Jan 2024 16:00:25 -0500 Subject: [PATCH 26/76] tidied formatting, filesystem --- etc/profile.d/help50.sh | 89 ++++++++++++-------------------------- opt/cs50/bin/_ansi | 13 ------ opt/cs50/bin/_sure | 14 ------ opt/cs50/bin/http-server | 2 + opt/cs50/bin/sqlite3 | 2 + opt/cs50/lib/cli | 72 ++++++++++++++++++++++++++++++ opt/cs50/lib/help50/make | 2 + opt/cs50/lib/help50/python | 2 + 8 files changed, 108 insertions(+), 88 deletions(-) delete mode 100755 opt/cs50/bin/_ansi delete mode 100755 opt/cs50/bin/_sure create mode 100644 opt/cs50/lib/cli diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 2c0f1c8..e353059 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -1,3 +1,5 @@ +. /opt/cs50/lib/cli + # Directory with helpers HELPERS="/opt/cs50/lib/help50" @@ -11,28 +13,28 @@ export HISTCONTROL="ignoredups" function _help50() { - # Exit status of last command + # Get exit status of last command local status=$? - # Append to history right away - history -a - - # Parse command line in case we want ${argv[0]} - read -a argv <<< $(history 1 | cut -c 8-) + # Get last command, independent of user's actual history + histfile=/tmp/help50.$$.history + HISTFILE=$histfile history -a + local argv0=$(HISTFILE=$histfile history 1 | cut -c 8- | awk '{print $1}') + rm --force $histfile # If no typescript yet - if [[ -z "$SCRIPT" ]]; then + if [[ -z $TYPESCRIPT ]]; then # Use this shell's PID as typescript's name, exporting so that subshells know script is already running - export SCRIPT="/tmp/help50.$$" + export TYPESCRIPT=/tmp/help50.$$.typescript # Make a typescript of everything displayed in terminal (without using exec, which breaks sudo); # --append avoids `bash: warning: command substitution: ignored null byte in input`; # --quiet suppresses `Script started...` - script --append --command "bash --login" --flush --quiet "$SCRIPT" + script --append --command "bash --login" --flush --quiet $TYPESCRIPT # Remove typescript before exiting this shell - rm --force "$SCRIPT" + rm --force $TYPESCRIPT # Now exit this shell too exit @@ -40,10 +42,15 @@ function _help50() { # If last command erred (and not ctl-z) # https://tldp.org/LDP/abs/html/exitcodes.html - if [[ $status -ne 0 && $status -ne 148 ]]; then + if [[ $status -ne 0 && $status -ne 148 && ! "$command" =~ ^\./ ]]; then + + # Ignore ./* if executable file + if [[ "$argv" =~ ^\./ && -f "$argv" && -x "$argv" ]]; then + break + fi # Read typescript from disk - local typescript=$(cat "$SCRIPT") + local typescript=$(cat $TYPESCRIPT) # Remove script's own output (if this is user's first command) typescript=$(echo "$typescript" | sed '1{/^Script started on .*/d}') @@ -71,9 +78,9 @@ function _help50() { typescript=$(echo "$typescript" | col -bp) # Try to get help - for helper in "$HELPERS"/*; do - if [[ -f "$helper" && -x "$helper" ]]; then - local help=$(. $helper <<< "$typescript") + for helper in $HELPERS/*; do + if [[ -f $helper && -x $helper ]]; then + local help=$($helper <<< "$typescript") if [[ -n "$help" ]]; then break fi @@ -82,63 +89,23 @@ function _help50() { if [[ -n "$help" ]]; then # If helpful _helpful "$help" elif [[ $status -ne 0 ]]; then # If helpless - _helpless "$text" + _helpless "$typescript" fi fi # Truncate typescript - truncate -s 0 "$SCRIPT" -} - -function _search() { - - # In $1 is path to find - if [[ $# -ne 1 ]]; then - return - fi - - # Find any $1 in descendants - paths=$(find $(pwd) -name "$1" 2> /dev/null) - if [[ -z "$paths" ]]; then - - # Find any $1 in ancestors - local dir="$(dirname "$(pwd)")" - while [[ "$dir" != "/" ]]; do - paths=$(find "$dir" -maxdepth 1 -name "$1") - if [[ -z "$paths" ]]; then - dir=$(dirname "$dir") - else - break - fi - done - if [[ -z "$paths" ]]; then - - # Find any $1 relative to `cd` - pushd "$(cd && pwd)" > /dev/null - paths=$(find $(pwd) -name "$1" 2> /dev/null) - popd > /dev/null - fi - fi - - # Count paths - count=$(echo "$paths" | grep -c .) - - # If just one - if [[ "$count" -eq 1 ]]; then - - # Resolve absolute path to relative path - realpath --relative-to=. "$(dirname "$paths")" - fi + truncate -s 0 "$TYPESCRIPT" } +# Default helpers if ! type _helpful >/dev/null 2>&1; then function _helpful() { - echo "$1" + local output=$(_ansi "$1") + echo -e "\033[7m${output}\033[27m" # Reverse video } fi - if ! type _helpless >/dev/null 2>&1; then - function _helpless() { :; } + function _helpless() { :; } # Silent fi export PROMPT_COMMAND="_help50${PROMPT_COMMAND:+; $PROMPT_COMMAND}" diff --git a/opt/cs50/bin/_ansi b/opt/cs50/bin/_ansi deleted file mode 100755 index fc7734b..0000000 --- a/opt/cs50/bin/_ansi +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# If command-line arguments -if [[ -t 0 ]]; then - input="$*" - -# If standard input -else - input=$(cat) -fi - -# https://www.gnu.org/software/termutils/manual/termutils-2.0/html_chapter/tput_1.html#SEC8 -echo "$input" | sed "s/\`\([^\`]*\)\`/$(tput smso)\1$(tput rmso)/g" diff --git a/opt/cs50/bin/_sure b/opt/cs50/bin/_sure deleted file mode 100755 index 74f688a..0000000 --- a/opt/cs50/bin/_sure +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -if [[ $# -ne 1 ]]; then - exit 1 -fi -prompt=$(echo "$1" | _ansi) -while true; do - read -p "$prompt [y/N] " -r - if [[ "${REPLY,,}" =~ ^(y|yes)$ ]]; then - exit 0 - elif [[ "${REPLY,,}" =~ ^(n|no)$ ]]; then - exit 1 - fi -done diff --git a/opt/cs50/bin/http-server b/opt/cs50/bin/http-server index 7bbcd9e..ea82a17 100755 --- a/opt/cs50/bin/http-server +++ b/opt/cs50/bin/http-server @@ -1,5 +1,7 @@ #!/bin/bash +. /opt/cs50/lib/cli + # Default options a="-a 0.0.0.0" c="-c-1" diff --git a/opt/cs50/bin/sqlite3 b/opt/cs50/bin/sqlite3 index 80557d8..b2dd85e 100755 --- a/opt/cs50/bin/sqlite3 +++ b/opt/cs50/bin/sqlite3 @@ -1,5 +1,7 @@ #!/bin/bash +. /opt/cs50/lib/cli + # If data is coming from stdin (pipe or redirection) if [[ -p /dev/stdin || ! -t 0 ]]; then /usr/local/bin/sqlite3 -nullvalue NULL -table "$@" < /dev/stdin diff --git a/opt/cs50/lib/cli b/opt/cs50/lib/cli new file mode 100644 index 0000000..9c1384d --- /dev/null +++ b/opt/cs50/lib/cli @@ -0,0 +1,72 @@ +function _ansi() { + + # If command-line arguments + if [[ -t 0 ]]; then + input="$*" + + # If standard input + else + input=$(cat) + fi + + # Format backticks as bold + bold=$(printf '\033[1m') + normal=$(printf '\033[22m') + echo "$input" | sed "s/\`\\([^\`]*\\)\`/${bold}\\1${normal}/g" +} + +function _search() { + + # In $1 is path to find + if [[ $# -ne 1 ]]; then + return + fi + + # Find any $1 in descendants + paths=$(find $(pwd) -name "$1" 2> /dev/null) + if [[ -z "$paths" ]]; then + + # Find any $1 in ancestors + local dir="$(dirname "$(pwd)")" + while [[ "$dir" != "/" ]]; do + paths=$(find "$dir" -maxdepth 1 -name "$1") + if [[ -z "$paths" ]]; then + dir=$(dirname "$dir") + else + break + fi + done + if [[ -z "$paths" ]]; then + + # Find any $1 relative to `cd` + pushd "$(cd && pwd)" > /dev/null + paths=$(find $(pwd) -name "$1" 2> /dev/null) + popd > /dev/null + fi + fi + + # Count paths + count=$(echo "$paths" | grep -c .) + + # If just one + if [[ "$count" -eq 1 ]]; then + + # Resolve absolute path to relative path + realpath --relative-to=. "$(dirname "$paths")" + fi +} + +function _sure() { + if [[ $# -ne 1 ]]; then + return 1 + fi + prompt=$(echo "$1" | _ansi) + while true; do + read -p "$prompt [y/N] " -r + if [[ "${REPLY,,}" =~ ^(y|yes)$ ]]; then + return 0 + elif [[ "${REPLY,,}" =~ ^(n|no)$ ]]; then + return 1 + fi + done +} diff --git a/opt/cs50/lib/help50/make b/opt/cs50/lib/help50/make index 7f9c913..f23c37e 100755 --- a/opt/cs50/lib/help50/make +++ b/opt/cs50/lib/help50/make @@ -1,5 +1,7 @@ #!/bin/bash +. /opt/cs50/lib/cli + output=$(cat) regex="make: Nothing to be done for '(.*)'" diff --git a/opt/cs50/lib/help50/python b/opt/cs50/lib/help50/python index 2474501..8248449 100755 --- a/opt/cs50/lib/help50/python +++ b/opt/cs50/lib/help50/python @@ -1,5 +1,7 @@ #!/bin/bash +. /opt/cs50/lib/cli + output=$(cat) regex="AttributeError: module 'cs50' has no attribute '(get_(float|int|string)|SQL)'$" From 72c3d53b0927872a4e88654d858b90ed3e6d6081 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 31 Jan 2024 17:32:25 -0500 Subject: [PATCH 27/76] capping size of help50 input --- etc/profile.d/help50.sh | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index e353059..191ea1a 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -1,3 +1,8 @@ +# If root +if [ "$(whoami)" == "root" ]; then + return +fi + . /opt/cs50/lib/cli # Directory with helpers @@ -5,7 +10,13 @@ HELPERS="/opt/cs50/lib/help50" # Disable yes, lest users type it at prompt if command -v yes &> /dev/null; then - alias yes=":" + function yes() { + if [[ -t 0 ]]; then + : + else + command yes + fi + } fi # Ignore duplicates (but not commands that begin with spaces) @@ -55,6 +66,9 @@ function _help50() { # Remove script's own output (if this is user's first command) typescript=$(echo "$typescript" | sed '1{/^Script started on .*/d}') + # Cap typescript at MIN(1K lines, 1M bytes), else `read` is slow + typescript=$(echo "$typescript" | head -n 1024 | cut -b 1-1048576) + # Remove any line continuations from command line local lines="" while IFS= read -r line || [[ -n "$line" ]]; do From eb6647d97206c232373e8717d44f618d641af0c7 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 2 Feb 2024 01:09:04 -0500 Subject: [PATCH 28/76] improved non-greedy matching --- opt/cs50/lib/help50/bash | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index ceaa0cd..224dec0 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -12,12 +12,13 @@ if [[ "$output" =~ $regex ]]; then fi fi -regex="bash: \./(.*): Is a directory" +regex="bash: \./([^:]*): Is a directory" if [[ "$output" =~ $regex ]]; then echo "Cannot execute a directory. Did you mean to \`cd\` into \`${BASH_REMATCH[1]}\` instead?" + exit fi -regex="bash: (./.*\.py): Permission denied" +regex="bash: (\./.*\.py): Permission denied" if [[ "$output" =~ $regex ]]; then # If file exists @@ -27,7 +28,7 @@ if [[ "$output" =~ $regex ]]; then fi fi -regex="bash: /.(.*): No such file or directory" +regex="bash: /\.([^:]*): No such file or directory" if [[ "$output" =~ $regex ]]; then # If file exists From 26facb72471dc4fb24766a26b3309c593b8774d7 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 2 Feb 2024 01:16:59 -0500 Subject: [PATCH 29/76] added helper for .c --- opt/cs50/lib/help50/bash | 10 ++++++++++ tests/baz.c | 0 tests/y.c | 0 3 files changed, 10 insertions(+) create mode 100644 tests/baz.c create mode 100644 tests/y.c diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index 224dec0..556b6e3 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -18,6 +18,16 @@ if [[ "$output" =~ $regex ]]; then exit fi +regex="bash: \./(([^:]*)\.c): Permission denied" +if [[ "$output" =~ $regex ]]; then + + # If file exists + if [[ -f "${BASH_REMATCH[1]}" ]]; then + echo "Did you mean to run \`make ${BASH_REMATCH[2]}\` and then \`./${BASH_REMATCH[2]}\`?" + exit + fi +fi + regex="bash: (\./.*\.py): Permission denied" if [[ "$output" =~ $regex ]]; then diff --git a/tests/baz.c b/tests/baz.c new file mode 100644 index 0000000..e69de29 diff --git a/tests/y.c b/tests/y.c new file mode 100644 index 0000000..e69de29 From 45cb7f30429a9c8334b347749b3e2a988373bf6b Mon Sep 17 00:00:00 2001 From: "yulai.linda@gmail.com" Date: Sat, 27 Apr 2024 22:12:30 -0400 Subject: [PATCH 30/76] use arm64 github runner for building arm image --- .github/workflows/main.yml | 41 ++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4c83cdf..c7abe02 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,11 +1,9 @@ on: push + jobs: - build: + build-amd64: runs-on: ubuntu-latest-64-cores steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 @@ -37,6 +35,41 @@ jobs: run: | docker push cs50/cli:amd64 + build-arm64: + needs: build-amd64 + runs-on: ubuntu-latest-64-cores-arm + steps: + - name: Install Docker + run: | + export DEBIAN_FRONTEND=noninteractive + sudo apt update + sudo apt install -y ca-certificates curl + sudo install -m 0755 -d /etc/apt/keyrings + sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc + sudo chmod a+r /etc/apt/keyrings/docker.asc + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt update + sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + sudo usermod -aG docker $USER + sudo apt install -y acl + sudo setfacl --modify user:$USER:rw /var/run/docker.sock + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log into Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Install Python + run: | + sudo apt install -y python3 + - name: Build for linux/arm64 uses: docker/build-push-action@v3 with: From 372761f60d628806486c5e6ef70d3253521f7ddc Mon Sep 17 00:00:00 2001 From: "yulai.linda@gmail.com" Date: Sat, 27 Apr 2024 22:45:20 -0400 Subject: [PATCH 31/76] build amd64 and arm64 docker images concurrently --- .github/workflows/main.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c7abe02..e1c742c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,7 +36,6 @@ jobs: docker push cs50/cli:amd64 build-arm64: - needs: build-amd64 runs-on: ubuntu-latest-64-cores-arm steps: - name: Install Docker @@ -87,6 +86,10 @@ jobs: run: | docker push cs50/cli:arm64 + finalize: + needs: [build-amd64, build-arm64] + runs-on: ubuntu-latest + steps: - name: Create multi-arch manifest and push to Docker Hub if: ${{ github.ref == 'refs/heads/main' }} run: | @@ -111,4 +114,4 @@ jobs: workflow_id: 'main.yml', ref: 'main' }); - } + } \ No newline at end of file From b9a5ee3b741fd882e2d68517fcb803411a6a1982 Mon Sep 17 00:00:00 2001 From: "yulai.linda@gmail.com" Date: Sat, 27 Apr 2024 22:55:10 -0400 Subject: [PATCH 32/76] create canary build --- .github/workflows/main.yml | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e1c742c..9e5e478 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,7 +26,9 @@ jobs: BUILDARCH=amd64 load: true platforms: linux/amd64 - tags: cs50/cli:amd64 + tags: | + cs50/cli:amd64 + cs50/cli:canary-amd64 cache-from: type=registry,ref=cs50/cli:amd64-buildcache cache-to: type=registry,ref=cs50/cli:amd64-buildcache,mode=max @@ -35,6 +37,10 @@ jobs: run: | docker push cs50/cli:amd64 + - name: Push linux/amd64 build to Docker Hub (canary) + run: | + docker push cs50/cli:canary-amd64 + build-arm64: runs-on: ubuntu-latest-64-cores-arm steps: @@ -77,7 +83,9 @@ jobs: BUILDARCH=arm64 load: true platforms: linux/arm64 - tags: cs50/cli:arm64 + tags: | + cs50/cli:arm64 + cs50/cli:canary-arm64 cache-from: type=registry,ref=cs50/cli:arm64-buildcache cache-to: type=registry,ref=cs50/cli:arm64-buildcache,mode=max @@ -86,10 +94,20 @@ jobs: run: | docker push cs50/cli:arm64 + - name: Push linux/arm64 build to Docker Hub (canary) + run: | + docker push cs50/cli:canary-arm64 + finalize: needs: [build-amd64, build-arm64] runs-on: ubuntu-latest steps: + - name: Log into Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Create multi-arch manifest and push to Docker Hub if: ${{ github.ref == 'refs/heads/main' }} run: | @@ -98,6 +116,13 @@ jobs: --amend cs50/cli:arm64 docker manifest push cs50/cli:latest + - name: Create multi-arch manifest and push to Docker Hub (canary) + run: | + docker manifest create cs50/cli:canary \ + --amend cs50/cli:canary-amd64 \ + --amend cs50/cli:canary-arm64 + docker manifest push cs50/cli:canary + - name: Re-deploy depdendents if: ${{ github.ref == 'refs/heads/main' }} uses: actions/github-script@v6 From 2919d87c1eb8203c261509af0071c34fc1674ea7 Mon Sep 17 00:00:00 2001 From: "yulai.linda@gmail.com" Date: Sat, 27 Apr 2024 23:02:17 -0400 Subject: [PATCH 33/76] updated workflow actions versions --- .github/workflows/main.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9e5e478..35ba217 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,10 +5,10 @@ jobs: runs-on: ubuntu-latest-64-cores steps: - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Log into Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -19,7 +19,7 @@ jobs: python-version: '3.11' - name: Build for linux/amd64 - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: build-args: | VCS_REF=${{ github.sha }} @@ -63,10 +63,10 @@ jobs: sudo setfacl --modify user:$USER:rw /var/run/docker.sock - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Log into Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -76,7 +76,7 @@ jobs: sudo apt install -y python3 - name: Build for linux/arm64 - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: build-args: | VCS_REF=${{ github.sha }} @@ -103,7 +103,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Log into Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} From f5b22c4a337d5006ea9e91b86e3007457ff129e6 Mon Sep 17 00:00:00 2001 From: "yulai.linda@gmail.com" Date: Sun, 28 Apr 2024 18:09:51 -0400 Subject: [PATCH 34/76] added comments --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 35ba217..2aff497 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,7 +44,7 @@ jobs: build-arm64: runs-on: ubuntu-latest-64-cores-arm steps: - - name: Install Docker + - name: Install Docker (remove once Docker is pre-installed on arm64 runners) run: | export DEBIAN_FRONTEND=noninteractive sudo apt update @@ -71,7 +71,7 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Install Python + - name: Install Python (replace with setup-python once available on arm64 runners) run: | sudo apt install -y python3 From fe134545288daa58ce2d9d49fd1fc4cbd6189125 Mon Sep 17 00:00:00 2001 From: "yulai.linda@gmail.com" Date: Thu, 2 May 2024 12:09:00 -0400 Subject: [PATCH 35/76] updated actions/github-script to version v7 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2aff497..a57c645 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -125,7 +125,7 @@ jobs: - name: Re-deploy depdendents if: ${{ github.ref == 'refs/heads/main' }} - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.DEPLOY50_PAT }} script: | From 8556c5311f423ec778da7c6a875c10c0f9427f05 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Mon, 6 May 2024 15:26:52 -0400 Subject: [PATCH 36/76] WIP --- etc/profile.d/help50.sh | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 191ea1a..9976340 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -39,7 +39,8 @@ function _help50() { # Use this shell's PID as typescript's name, exporting so that subshells know script is already running export TYPESCRIPT=/tmp/help50.$$.typescript - # Make a typescript of everything displayed in terminal (without using exec, which breaks sudo); + # Make a typescript of everything displayed in terminal, + # without using exec, which causes sudo to hang, or tee, which triggers "Output is not a terminal" in vim; # --append avoids `bash: warning: command substitution: ignored null byte in input`; # --quiet suppresses `Script started...` script --append --command "bash --login" --flush --quiet $TYPESCRIPT @@ -51,9 +52,9 @@ function _help50() { exit fi - # If last command erred (and not ctl-z) + # If last command erred (and is not ctl-z) # https://tldp.org/LDP/abs/html/exitcodes.html - if [[ $status -ne 0 && $status -ne 148 && ! "$command" =~ ^\./ ]]; then + if [[ $status -ne 0 && $status -ne 148 && ]]; then # Ignore ./* if executable file if [[ "$argv" =~ ^\./ && -f "$argv" && -x "$argv" ]]; then @@ -122,4 +123,9 @@ if ! type _helpless >/dev/null 2>&1; then function _helpless() { :; } # Silent fi -export PROMPT_COMMAND="_help50${PROMPT_COMMAND:+; $PROMPT_COMMAND}" +if [[ ! "$PROMPT_COMMAND" =~ ^_help50; ]]; then + export PROMPT_COMMAND="_help50;${PROMPT_COMMAND:+; $PROMPT_COMMAND}" +fi + +# TODO: +# help on, off From 5f11fefcb9338657ab551eebaeac5c1bb6720616 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Mon, 6 May 2024 15:58:08 -0400 Subject: [PATCH 37/76] WIP --- etc/profile.d/help50.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 9976340..aab3f0d 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -3,7 +3,9 @@ if [ "$(whoami)" == "root" ]; then return fi -. /opt/cs50/lib/cli +# TEMP +#. /opt/cs50/lib/cli +. opt/cs50/lib/cli # Directory with helpers HELPERS="/opt/cs50/lib/help50" @@ -54,7 +56,7 @@ function _help50() { # If last command erred (and is not ctl-z) # https://tldp.org/LDP/abs/html/exitcodes.html - if [[ $status -ne 0 && $status -ne 148 && ]]; then + if [[ $status -ne 0 && $status -ne 148 ]]; then # Ignore ./* if executable file if [[ "$argv" =~ ^\./ && -f "$argv" && -x "$argv" ]]; then @@ -123,9 +125,10 @@ if ! type _helpless >/dev/null 2>&1; then function _helpless() { :; } # Silent fi -if [[ ! "$PROMPT_COMMAND" =~ ^_help50; ]]; then - export PROMPT_COMMAND="_help50;${PROMPT_COMMAND:+; $PROMPT_COMMAND}" -fi +#if [[ ! "$PROMPT_COMMAND" =~ ^_help50\; ]]; then + #export PROMPT_COMMAND="_help50${PROMPT_COMMAND:+; $PROMPT_COMMAND}" +#fi +export PROMPT_COMMAND=_help50 # TODO: # help on, off From a91969f972d4a5b0f953af7197ca7a1c274e9c4a Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Mon, 6 May 2024 20:21:02 -0400 Subject: [PATCH 38/76] WIP2 --- .../help50.sh => opt/cs50/bin/help50 | 81 +++++++++++++++++-- 1 file changed, 76 insertions(+), 5 deletions(-) rename etc/profile.d/help50.sh => opt/cs50/bin/help50 (72%) mode change 100644 => 100755 diff --git a/etc/profile.d/help50.sh b/opt/cs50/bin/help50 old mode 100644 new mode 100755 similarity index 72% rename from etc/profile.d/help50.sh rename to opt/cs50/bin/help50 index aab3f0d..4f34d29 --- a/etc/profile.d/help50.sh +++ b/opt/cs50/bin/help50 @@ -1,14 +1,85 @@ +#!/bin/bash + # If root -if [ "$(whoami)" == "root" ]; then - return +if [[ `id -u` -eq 0 ]]; then + exit 1 fi -# TEMP -#. /opt/cs50/lib/cli +function _start() { + + # If already helping + if [[ -n "$HELP50" ]]; then + exit 0 + fi + + # PID of shell to help + export HELP50="$PPID" + + # Start `script` in background + set -o monitor + script --append --command "bash --login && exit 1" --flush --quiet --return "/tmp/help50.$HELP50.typescript" & + + # Remember PID of `script`, in case user wants to turn off + echo $! > "/tmp/help50.$HELP50.pid" + + # Foreground `script`, without echoing command + fg > /dev/null + + # If `script` exited on its own, as via ctl-d or `logout`, then kill parent shell + if [[ $? -ne 0 ]]; then + kill -SIGHUP "$PPID" + fi +} + +function _stop() { + + # If not helping + if [[ -z "$HELP50" ]]; then + exit 0 + fi + + # Kill `script` + pid=$(cat "/tmp/help50.$HELP50.pid") + kill -SIGTERM "$pid" + + # No longer helping + rm --force "/tmp/help50.$HELP50.pid" + unset HELP50 +} + +# Parse argument +if [[ "$1" == "off" ]]; then + _stop +elif [[ "$1" == "on" ]]; then + _start +elif [[ "$1" == "status" ]]; then + if [[ -n "$HELP50" ]]; then + echo "Running" + exit 0 + else + echo "Not running" + exit 1 + fi +else + echo "Usage: $0 [off|on|status]" + exit 1 +fi + +exit 0 + + + + + +# Helpers +# TODO +#. /opt/cs50/lib/help50 . opt/cs50/lib/cli # Directory with helpers -HELPERS="/opt/cs50/lib/help50" +# TODO +#HELPERS="/opt/cs50/lib/help50" +HELPERS="/mnt/opt/cs50/lib/help50" # Disable yes, lest users type it at prompt if command -v yes &> /dev/null; then From 73601b99cbb7620e1454dcde6d108d8488fe40e9 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Mon, 6 May 2024 21:46:09 -0400 Subject: [PATCH 39/76] WIP2 --- etc/profile.d/help50.sh | 116 ++++++++++++++++++++++++++++++++++++++++ opt/cs50/bin/help50 | 60 +++++++++++---------- 2 files changed, 148 insertions(+), 28 deletions(-) create mode 100644 etc/profile.d/help50.sh diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh new file mode 100644 index 0000000..473fa8b --- /dev/null +++ b/etc/profile.d/help50.sh @@ -0,0 +1,116 @@ +# Helpers +# TODO +#. /opt/cs50/lib/help50 +#. opt/cs50/lib/cli + +if [[ -z "$HELP50" ]]; then + return +fi + +echo HEREEEEEE + +# Directory with helpers +# TODO +#HELPERS="/opt/cs50/lib/help50" +HELPERS="/mnt/opt/cs50/lib/help50" + +# Disable yes, lest users type it at prompt +if command -v yes &> /dev/null; then + function yes() { + if [[ -t 0 ]]; then + : + else + command yes + fi + } +fi + +# Ignore duplicates (but not commands that begin with spaces) +export HISTCONTROL="ignoredups" + +function _help50() { + + # Get exit status of last command + local status=$? + + # Get last command, independent of user's actual history + histfile=/tmp/help50.$$.history + HISTFILE=$histfile history -a + local argv0=$(HISTFILE=$histfile history 1 | cut -c 8- | awk '{print $1}') + rm --force $histfile + + # If last command erred (and is not ctl-z) + # https://tldp.org/LDP/abs/html/exitcodes.html + if [[ $status -ne 0 && $status -ne 148 ]]; then + + # Ignore ./* if executable file + if [[ "$argv" =~ ^\./ && -f "$argv" && -x "$argv" ]]; then + break + fi + + # Read typescript from disk + local typescript=$(cat /tmp/help50.$HELP50.typescript) + + # Remove script's own output (if this is user's first command) + typescript=$(echo "$typescript" | sed '1{/^Script started on .*/d}') + + # Cap typescript at MIN(1K lines, 1M bytes), else `read` is slow + typescript=$(echo "$typescript" | head -n 1024 | cut -b 1-1048576) + + # Remove any line continuations from command line + local lines="" + while IFS= read -r line || [[ -n "$line" ]]; do + if [[ -z $done && $line =~ \\$ ]]; then + lines+="${line%\\}" + else + lines+="$line"$'\n' + local done=1 + fi + done <<< "$typescript" + typescript="$lines" + + # Remove command line from typescript + typescript=$(echo "$typescript" | sed '1d') + + # Remove ANSI characters + typescript=$(echo "$typescript" | ansi2txt) + + # Remove control characters + # https://superuser.com/a/237154 + typescript=$(echo "$typescript" | col -bp) + + # Try to get help + for helper in $HELPERS/*; do + if [[ -f $helper && -x $helper ]]; then + local help=$($helper <<< "$typescript") + if [[ -n "$help" ]]; then + break + fi + fi + done + if [[ -n "$help" ]]; then # If helpful + _helpful "$help" + elif [[ $status -ne 0 ]]; then # If helpless + _helpless "$typescript" + fi + fi + + # Truncate typescript + truncate -s 0 "/tmp/help50.$HELP50.typescript" +} + +# Default helpers +if ! type _helpful >/dev/null 2>&1; then + function _helpful() { + local output=$(_ansi "$1") + echo -e "\033[7m${output}\033[27m" # Reverse video + } +fi +if ! type _helpless >/dev/null 2>&1; then + function _helpless() { :; } # Silent +fi + +#if [[ ! "$PROMPT_COMMAND" =~ ^_help50\; ]]; then + #export PROMPT_COMMAND="_help50${PROMPT_COMMAND:+; $PROMPT_COMMAND}" +#fi +export PROMPT_COMMAND=_help50 diff --git a/opt/cs50/bin/help50 b/opt/cs50/bin/help50 index 4f34d29..37a8a43 100755 --- a/opt/cs50/bin/help50 +++ b/opt/cs50/bin/help50 @@ -5,7 +5,23 @@ if [[ `id -u` -eq 0 ]]; then exit 1 fi -function _start() { +function _off() { + + # If not helping + if [[ -z "$HELP50" ]]; then + exit 0 + fi + + # Kill `script` + pid=$(cat "/tmp/help50.$HELP50.pid") + kill -SIGTERM "$pid" + + # No longer helping + rm --force /tmp/help50.$HELP50.* + unset HELP50 +} + +function _on() { # If already helping if [[ -n "$HELP50" ]]; then @@ -24,44 +40,32 @@ function _start() { # Foreground `script`, without echoing command fg > /dev/null + status=$? - # If `script` exited on its own, as via ctl-d or `logout`, then kill parent shell - if [[ $? -ne 0 ]]; then - kill -SIGHUP "$PPID" - fi -} + # No longer helping + rm --force /tmp/help50.$HELP50.* -function _stop() { + # If `script` was killed, in which case `exit 1` above won't execute + if [[ $status -ne 1 ]]; then - # If not helping - if [[ -z "$HELP50" ]]; then - exit 0 - fi + # Without this, prompt ends up below and to right of "Session terminated, killing shell... ...killed." + echo -e "\r" - # Kill `script` - pid=$(cat "/tmp/help50.$HELP50.pid") - kill -SIGTERM "$pid" + # Else if `script` exited on its own, as via ctl-d or `logout` + else - # No longer helping - rm --force "/tmp/help50.$HELP50.pid" - unset HELP50 + # Kill parent shell, since user presumably wants to exit + kill -SIGHUP "$PPID" + fi } # Parse argument if [[ "$1" == "off" ]]; then - _stop + _off elif [[ "$1" == "on" ]]; then - _start -elif [[ "$1" == "status" ]]; then - if [[ -n "$HELP50" ]]; then - echo "Running" - exit 0 - else - echo "Not running" - exit 1 - fi + _on else - echo "Usage: $0 [off|on|status]" + echo "Usage: $0 [off|on]" exit 1 fi From d6fc53add8deabe971fd75cae17f6e1d9eac0b8a Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Tue, 7 May 2024 09:55:27 -0400 Subject: [PATCH 40/76] WIP3 --- opt/cs50/bin/help50 | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/opt/cs50/bin/help50 b/opt/cs50/bin/help50 index 37a8a43..4958686 100755 --- a/opt/cs50/bin/help50 +++ b/opt/cs50/bin/help50 @@ -6,19 +6,11 @@ if [[ `id -u` -eq 0 ]]; then fi function _off() { - - # If not helping - if [[ -z "$HELP50" ]]; then - exit 0 - fi - - # Kill `script` - pid=$(cat "/tmp/help50.$HELP50.pid") - kill -SIGTERM "$pid" - - # No longer helping - rm --force /tmp/help50.$HELP50.* - unset HELP50 + for pid in /tmp/help50.*.pid + do + kill -SIGTERM $(cat "$pid") + done + touch /tmp/help50.lock } function _on() { @@ -28,12 +20,15 @@ function _on() { exit 0 fi - # PID of shell to help - export HELP50="$PPID" + # Remove any lockfile + rm --force /tmp/help50.lock + + # PID of parent shell to help + local HELP50="$PPID" # Start `script` in background set -o monitor - script --append --command "bash --login && exit 1" --flush --quiet --return "/tmp/help50.$HELP50.typescript" & + HELP50="$HELP50" script --append --command "bash --login && exit 1" --flush --quiet --return "/tmp/help50.$HELP50.typescript" & # Remember PID of `script`, in case user wants to turn off echo $! > "/tmp/help50.$HELP50.pid" @@ -55,7 +50,7 @@ function _on() { else # Kill parent shell, since user presumably wants to exit - kill -SIGHUP "$PPID" + kill -SIGHUP "$HELP50" fi } From 0c96044da76ba4900c440d048f7ab4cb7514dbc9 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Tue, 7 May 2024 16:09:49 -0400 Subject: [PATCH 41/76] WIP4 --- etc/profile.d/help50.sh | 25 ++--- opt/cs50/bin/help50 | 221 +++++++++++----------------------------- 2 files changed, 69 insertions(+), 177 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 473fa8b..be8592a 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -1,18 +1,13 @@ -# Helpers -# TODO -#. /opt/cs50/lib/help50 -#. opt/cs50/lib/cli - +# If not started if [[ -z "$HELP50" ]]; then return fi -echo HEREEEEEE - # Directory with helpers -# TODO -#HELPERS="/opt/cs50/lib/help50" -HELPERS="/mnt/opt/cs50/lib/help50" +HELPERS="/opt/cs50/lib/help50" + +# Library +. /opt/cs50/lib/cli # Disable yes, lest users type it at prompt if command -v yes &> /dev/null; then @@ -96,7 +91,7 @@ function _help50() { fi # Truncate typescript - truncate -s 0 "/tmp/help50.$HELP50.typescript" + truncate -s 0 /tmp/help50.$HELP50.typescript } # Default helpers @@ -107,10 +102,10 @@ if ! type _helpful >/dev/null 2>&1; then } fi if ! type _helpless >/dev/null 2>&1; then - function _helpless() { :; } # Silent + #function _helpless() { :; } # Silent + function _helpless() { + echo TODO + } fi -#if [[ ! "$PROMPT_COMMAND" =~ ^_help50\; ]]; then - #export PROMPT_COMMAND="_help50${PROMPT_COMMAND:+; $PROMPT_COMMAND}" -#fi export PROMPT_COMMAND=_help50 diff --git a/opt/cs50/bin/help50 b/opt/cs50/bin/help50 index 4958686..a643da2 100755 --- a/opt/cs50/bin/help50 +++ b/opt/cs50/bin/help50 @@ -5,33 +5,42 @@ if [[ `id -u` -eq 0 ]]; then exit 1 fi -function _off() { - for pid in /tmp/help50.*.pid - do - kill -SIGTERM $(cat "$pid") - done +function _disable() { touch /tmp/help50.lock } -function _on() { +function _is-enabled() { + if [[ -f /tmp/help50.lock ]]; then + echo disabled + return 1 + else + echo enabled + return 0 + fi +} + +function _enable() { + rm --force /tmp/help50.lock +} + +function _start() { # If already helping if [[ -n "$HELP50" ]]; then - exit 0 + return 0 fi - # Remove any lockfile - rm --force /tmp/help50.lock - # PID of parent shell to help - local HELP50="$PPID" + local HELP50=$PPID - # Start `script` in background + # Start `script` in background, using ; instead of && for command, + # else if user logs out (as via ctl-d) after a non-0 command, bash exits with 127 set -o monitor - HELP50="$HELP50" script --append --command "bash --login && exit 1" --flush --quiet --return "/tmp/help50.$HELP50.typescript" & + HELP50=$HELP50 script --command "bash --login ; exit 1" --flush --quiet --return /tmp/help50.$HELP50.typescript & # Remember PID of `script`, in case user wants to turn off - echo $! > "/tmp/help50.$HELP50.pid" + echo $! > /tmp/help50.$HELP50.pid + ps aux # Foreground `script`, without echoing command fg > /dev/null @@ -40,165 +49,53 @@ function _on() { # No longer helping rm --force /tmp/help50.$HELP50.* + # Temporary + if [[ $status -eq 150 ]]; then + echo "[$status]" + echo "Sorry, something's wrong! Please let sysadmins@cs50.harvard.edu know." + # If `script` was killed, in which case `exit 1` above won't execute - if [[ $status -ne 1 ]]; then + elif [[ $status -ne 1 ]]; then # Without this, prompt ends up below and to right of "Session terminated, killing shell... ...killed." echo -e "\r" + echo "[[$status]]" # Else if `script` exited on its own, as via ctl-d or `logout` else # Kill parent shell, since user presumably wants to exit - kill -SIGHUP "$HELP50" + echo "[[[$status]]]" + kill -SIGHUP $HELP50 fi } -# Parse argument -if [[ "$1" == "off" ]]; then - _off -elif [[ "$1" == "on" ]]; then - _on -else - echo "Usage: $0 [off|on]" - exit 1 -fi - -exit 0 - - - - - -# Helpers -# TODO -#. /opt/cs50/lib/help50 -. opt/cs50/lib/cli - -# Directory with helpers -# TODO -#HELPERS="/opt/cs50/lib/help50" -HELPERS="/mnt/opt/cs50/lib/help50" - -# Disable yes, lest users type it at prompt -if command -v yes &> /dev/null; then - function yes() { - if [[ -t 0 ]]; then - : - else - command yes - fi - } -fi - -# Ignore duplicates (but not commands that begin with spaces) -export HISTCONTROL="ignoredups" - -function _help50() { - - # Get exit status of last command - local status=$? - - # Get last command, independent of user's actual history - histfile=/tmp/help50.$$.history - HISTFILE=$histfile history -a - local argv0=$(HISTFILE=$histfile history 1 | cut -c 8- | awk '{print $1}') - rm --force $histfile - - # If no typescript yet - if [[ -z $TYPESCRIPT ]]; then - - # Use this shell's PID as typescript's name, exporting so that subshells know script is already running - export TYPESCRIPT=/tmp/help50.$$.typescript - - # Make a typescript of everything displayed in terminal, - # without using exec, which causes sudo to hang, or tee, which triggers "Output is not a terminal" in vim; - # --append avoids `bash: warning: command substitution: ignored null byte in input`; - # --quiet suppresses `Script started...` - script --append --command "bash --login" --flush --quiet $TYPESCRIPT - - # Remove typescript before exiting this shell - rm --force $TYPESCRIPT - - # Now exit this shell too - exit - fi - - # If last command erred (and is not ctl-z) - # https://tldp.org/LDP/abs/html/exitcodes.html - if [[ $status -ne 0 && $status -ne 148 ]]; then - - # Ignore ./* if executable file - if [[ "$argv" =~ ^\./ && -f "$argv" && -x "$argv" ]]; then - break - fi - - # Read typescript from disk - local typescript=$(cat $TYPESCRIPT) - - # Remove script's own output (if this is user's first command) - typescript=$(echo "$typescript" | sed '1{/^Script started on .*/d}') - - # Cap typescript at MIN(1K lines, 1M bytes), else `read` is slow - typescript=$(echo "$typescript" | head -n 1024 | cut -b 1-1048576) - - # Remove any line continuations from command line - local lines="" - while IFS= read -r line || [[ -n "$line" ]]; do - if [[ -z $done && $line =~ \\$ ]]; then - lines+="${line%\\}" - else - lines+="$line"$'\n' - local done=1 - fi - done <<< "$typescript" - typescript="$lines" - - # Remove command line from typescript - typescript=$(echo "$typescript" | sed '1d') - - # Remove ANSI characters - typescript=$(echo "$typescript" | ansi2txt) - - # Remove control characters - # https://superuser.com/a/237154 - typescript=$(echo "$typescript" | col -bp) - - # Try to get help - for helper in $HELPERS/*; do - if [[ -f $helper && -x $helper ]]; then - local help=$($helper <<< "$typescript") - if [[ -n "$help" ]]; then - break - fi - fi - done - if [[ -n "$help" ]]; then # If helpful - _helpful "$help" - elif [[ $status -ne 0 ]]; then # If helpless - _helpless "$typescript" - fi - fi - - # Truncate typescript - truncate -s 0 "$TYPESCRIPT" +function _stop() { + for pid in /tmp/help50.*.pid + do + kill -SIGTERM $(cat "$pid") + done } -# Default helpers -if ! type _helpful >/dev/null 2>&1; then - function _helpful() { - local output=$(_ansi "$1") - echo -e "\033[7m${output}\033[27m" # Reverse video - } -fi -if ! type _helpless >/dev/null 2>&1; then - function _helpless() { :; } # Silent -fi - -#if [[ ! "$PROMPT_COMMAND" =~ ^_help50\; ]]; then - #export PROMPT_COMMAND="_help50${PROMPT_COMMAND:+; $PROMPT_COMMAND}" -#fi -export PROMPT_COMMAND=_help50 - -# TODO: -# help on, off +# Parse argument +case "$1" in + disable) + _disable + ;; + enable) + _enable + ;; + is-enabled) + _is-enabled + ;; + start) + _start + ;; + stop) + _stop + ;; + *) + echo "Usage: $0 [disable|enable|is-enabled|start|stop]" + exit 1 + ;; +esac From 72dd67f793db0989910e2bfea1e71c15f9124475 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 8 May 2024 10:19:34 -0400 Subject: [PATCH 42/76] WIP5 --- opt/cs50/bin/help50 | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/opt/cs50/bin/help50 b/opt/cs50/bin/help50 index a643da2..ddd9712 100755 --- a/opt/cs50/bin/help50 +++ b/opt/cs50/bin/help50 @@ -6,11 +6,11 @@ if [[ `id -u` -eq 0 ]]; then fi function _disable() { - touch /tmp/help50.lock + touch /var/tmp/help50.lock } function _is-enabled() { - if [[ -f /tmp/help50.lock ]]; then + if [[ -f /var/tmp/help50.lock ]]; then echo disabled return 1 else @@ -20,7 +20,7 @@ function _is-enabled() { } function _enable() { - rm --force /tmp/help50.lock + rm --force /var/tmp/help50.lock } function _start() { @@ -36,18 +36,19 @@ function _start() { # Start `script` in background, using ; instead of && for command, # else if user logs out (as via ctl-d) after a non-0 command, bash exits with 127 set -o monitor - HELP50=$HELP50 script --command "bash --login ; exit 1" --flush --quiet --return /tmp/help50.$HELP50.typescript & + HELP50=$HELP50 script --command "bash --login ; exit 1" --flush --quiet --return /var/tmp/help50.$HELP50.typescript & + local pid=$! # Remember PID of `script`, in case user wants to turn off - echo $! > /tmp/help50.$HELP50.pid - ps aux + echo $pid > /var/tmp/help50.$HELP50.pid # Foreground `script`, without echoing command - fg > /dev/null - status=$? + #fg > /dev/null + wait + local status=$? # No longer helping - rm --force /tmp/help50.$HELP50.* + rm --force /var/tmp/help50.$HELP50.* # Temporary if [[ $status -eq 150 ]]; then @@ -71,7 +72,7 @@ function _start() { } function _stop() { - for pid in /tmp/help50.*.pid + for pid in /var/tmp/help50.*.pid do kill -SIGTERM $(cat "$pid") done From d5a938cffb87505af3218a607bf9b9de7ec00e0d Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 8 May 2024 11:21:44 -0400 Subject: [PATCH 43/76] WIP6 --- opt/cs50/bin/help50 | 35 ++++++++++------------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/opt/cs50/bin/help50 b/opt/cs50/bin/help50 index ddd9712..138a28e 100755 --- a/opt/cs50/bin/help50 +++ b/opt/cs50/bin/help50 @@ -6,11 +6,11 @@ if [[ `id -u` -eq 0 ]]; then fi function _disable() { - touch /var/tmp/help50.lock + touch /tmp/help50.lock } function _is-enabled() { - if [[ -f /var/tmp/help50.lock ]]; then + if [[ -f /tmp/help50.lock ]]; then echo disabled return 1 else @@ -20,7 +20,7 @@ function _is-enabled() { } function _enable() { - rm --force /var/tmp/help50.lock + rm --force /tmp/help50.lock } function _start() { @@ -36,46 +36,31 @@ function _start() { # Start `script` in background, using ; instead of && for command, # else if user logs out (as via ctl-d) after a non-0 command, bash exits with 127 set -o monitor - HELP50=$HELP50 script --command "bash --login ; exit 1" --flush --quiet --return /var/tmp/help50.$HELP50.typescript & - local pid=$! - - # Remember PID of `script`, in case user wants to turn off - echo $pid > /var/tmp/help50.$HELP50.pid - - # Foreground `script`, without echoing command - #fg > /dev/null - wait + HELP50=$HELP50 script --append --command "bash --login ; exit 1" --flush --quiet --return /tmp/help50.$HELP50.typescript local status=$? # No longer helping - rm --force /var/tmp/help50.$HELP50.* - - # Temporary - if [[ $status -eq 150 ]]; then - echo "[$status]" - echo "Sorry, something's wrong! Please let sysadmins@cs50.harvard.edu know." + rm --force /tmp/help50.$HELP50.* # If `script` was killed, in which case `exit 1` above won't execute - elif [[ $status -ne 1 ]]; then + if [[ $status -ne 1 ]]; then # Without this, prompt ends up below and to right of "Session terminated, killing shell... ...killed." echo -e "\r" - echo "[[$status]]" # Else if `script` exited on its own, as via ctl-d or `logout` else # Kill parent shell, since user presumably wants to exit - echo "[[[$status]]]" kill -SIGHUP $HELP50 fi } function _stop() { - for pid in /var/tmp/help50.*.pid - do - kill -SIGTERM $(cat "$pid") - done + local ppid=$(ps -o ppid= -p $$) # bash --login + local gppid=$(ps -o ppid= -p $ppid) # sh -c + local ggppid=$(ps -o ppid= -p $gppid) # script + kill -SIGTERM $ggppid } # Parse argument From f96d245a11e9edce2fccbbea5c4db2b08821d831 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 8 May 2024 11:33:32 -0400 Subject: [PATCH 44/76] WIP7 --- etc/profile.d/cli.sh | 7 ++++++- etc/profile.d/help50.sh | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/etc/profile.d/cli.sh b/etc/profile.d/cli.sh index 779b08e..9727517 100644 --- a/etc/profile.d/cli.sh +++ b/etc/profile.d/cli.sh @@ -1,5 +1,5 @@ # If not root -if [ "$(whoami)" != "root" ]; then +if [ `id -u` -ne 0 ]; then # $PATH export PATH="/opt/cs50/bin":"/opt/bin":"$PATH" @@ -60,4 +60,9 @@ if [ "$(whoami)" != "root" ]; then # Valgrind export VALGRIND_OPTS="--memcheck:leak-check=full --memcheck:show-leak-kinds=all --memcheck:track-origins=yes" + + # Start help50 if enabled + if help50 is-enabled > /dev/null; then + help50 start + fi fi diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index be8592a..676e905 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -34,9 +34,9 @@ function _help50() { local argv0=$(HISTFILE=$histfile history 1 | cut -c 8- | awk '{print $1}') rm --force $histfile - # If last command erred (and is not ctl-z) + # If last command erred (and is not ctl-c or ctl-z) # https://tldp.org/LDP/abs/html/exitcodes.html - if [[ $status -ne 0 && $status -ne 148 ]]; then + if [[ $status -ne 0 && $status -ne 130 && $status -ne 148 ]]; then # Ignore ./* if executable file if [[ "$argv" =~ ^\./ && -f "$argv" && -x "$argv" ]]; then From 4b19fc42e3d353d91885cc1e4ac85e6d68e30b28 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 8 May 2024 12:41:03 -0400 Subject: [PATCH 45/76] added WORKDIR --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1090923..aeff5be 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ rebuild: docker build --build-arg TAG=$(TAG) --build-arg VCS_REF=$(shell git rev-parse HEAD) --no-cache --tag cs50/cli:$(TAG) . run: - docker run --env LANG=$(LANG) --env LOCAL_WORKSPACE_FOLDER="$(PWD)" --interactive --publish-all --rm --security-opt seccomp=unconfined --tty --volume "$(PWD)":/mnt --volume /var/run/docker.sock:/var/run/docker-host.sock --workdir /mnt cs50/cli:$(TAG) bash --login || true + docker run --env LANG=$(LANG) --env LOCAL_WORKSPACE_FOLDER="$(PWD)" --env WORKDIR=/mnt --interactive --publish-all --rm --security-opt seccomp=unconfined --tty --volume "$(PWD)":/mnt --volume /var/run/docker.sock:/var/run/docker-host.sock --workdir /mnt cs50/cli:$(TAG) bash --login || true squash: depends docker-squash --tag cs50/cli:$(TAG) cs50/cli:$(TAG) From 28014567eb7e0c01438af246b26a8626a2cc8def Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 8 May 2024 12:54:51 -0400 Subject: [PATCH 46/76] WIP7 --- etc/profile.d/help50.sh | 7 ++----- opt/cs50/bin/help50 | 6 +++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 676e905..f33f7c6 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -98,14 +98,11 @@ function _help50() { if ! type _helpful >/dev/null 2>&1; then function _helpful() { local output=$(_ansi "$1") - echo -e "\033[7m${output}\033[27m" # Reverse video + echo -e "\033[33m${output}\033[39m" # Yellow } fi if ! type _helpless >/dev/null 2>&1; then - #function _helpless() { :; } # Silent - function _helpless() { - echo TODO - } + function _helpless() { :; } # Silent fi export PROMPT_COMMAND=_help50 diff --git a/opt/cs50/bin/help50 b/opt/cs50/bin/help50 index 138a28e..7e46f6d 100755 --- a/opt/cs50/bin/help50 +++ b/opt/cs50/bin/help50 @@ -57,9 +57,9 @@ function _start() { } function _stop() { - local ppid=$(ps -o ppid= -p $$) # bash --login - local gppid=$(ps -o ppid= -p $ppid) # sh -c - local ggppid=$(ps -o ppid= -p $gppid) # script + local ppid=$(ps -o ppid= -p $$) # bash --login + local gppid=$(ps -o ppid= -p $ppid) # sh -c + local ggppid=$(ps -o ppid= -p $gppid) # script kill -SIGTERM $ggppid } From 35dd708fc8fe2125732848f094f83d43fa86242d Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 8 May 2024 15:16:49 -0400 Subject: [PATCH 47/76] WIP8 --- etc/profile.d/help50.sh | 24 ++++++++++++++---------- opt/cs50/bin/help50 | 7 +++++++ opt/cs50/lib/cli | 36 ++++++++++-------------------------- opt/cs50/lib/help50/bash | 8 ++++++-- opt/cs50/lib/help50/cd | 19 +++++++++++++++++++ 5 files changed, 56 insertions(+), 38 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index f33f7c6..490e09f 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -10,15 +10,19 @@ HELPERS="/opt/cs50/lib/help50" . /opt/cs50/lib/cli # Disable yes, lest users type it at prompt -if command -v yes &> /dev/null; then - function yes() { - if [[ -t 0 ]]; then - : - else - command yes - fi - } -fi +function yes() { + if [[ -t 0 ]]; then + _alert "That was a rhetorical question. :)" + else + command yes + fi +} +alias y=yes + +# Don't override `n`, though +function no() { + _alert "That was a rhetorical question. :)" +} # Ignore duplicates (but not commands that begin with spaces) export HISTCONTROL="ignoredups" @@ -98,7 +102,7 @@ function _help50() { if ! type _helpful >/dev/null 2>&1; then function _helpful() { local output=$(_ansi "$1") - echo -e "\033[33m${output}\033[39m" # Yellow + _alert "$output" } fi if ! type _helpless >/dev/null 2>&1; then diff --git a/opt/cs50/bin/help50 b/opt/cs50/bin/help50 index 7e46f6d..1120587 100755 --- a/opt/cs50/bin/help50 +++ b/opt/cs50/bin/help50 @@ -57,6 +57,13 @@ function _start() { } function _stop() { + + # If not helping + if [[ -z "$HELP50" ]]; then + return 0 + fi + + # Kill grandparent process (i.e., `script` itself) local ppid=$(ps -o ppid= -p $$) # bash --login local gppid=$(ps -o ppid= -p $ppid) # sh -c local ggppid=$(ps -o ppid= -p $gppid) # script diff --git a/opt/cs50/lib/cli b/opt/cs50/lib/cli index 9c1384d..0ea76be 100644 --- a/opt/cs50/lib/cli +++ b/opt/cs50/lib/cli @@ -1,3 +1,7 @@ +function _alert() { + echo -e "\033[33m${1}\033[39m" # Yellow +} + function _ansi() { # If command-line arguments @@ -10,8 +14,8 @@ function _ansi() { fi # Format backticks as bold - bold=$(printf '\033[1m') - normal=$(printf '\033[22m') + local bold=$(printf '\033[1m') + local normal=$(printf '\033[22m') echo "$input" | sed "s/\`\\([^\`]*\\)\`/${bold}\\1${normal}/g" } @@ -22,31 +26,11 @@ function _search() { return fi - # Find any $1 in descendants - paths=$(find $(pwd) -name "$1" 2> /dev/null) - if [[ -z "$paths" ]]; then - - # Find any $1 in ancestors - local dir="$(dirname "$(pwd)")" - while [[ "$dir" != "/" ]]; do - paths=$(find "$dir" -maxdepth 1 -name "$1") - if [[ -z "$paths" ]]; then - dir=$(dirname "$dir") - else - break - fi - done - if [[ -z "$paths" ]]; then - - # Find any $1 relative to `cd` - pushd "$(cd && pwd)" > /dev/null - paths=$(find $(pwd) -name "$1" 2> /dev/null) - popd > /dev/null - fi - fi + # Find any $1 in descendants of $WORKDIR + paths=$(find "$WORKDIR" -name "$1" -printf "%T+ %p\n" | sort -nr | awk '{print $2}' 2> /dev/null) # Count paths - count=$(echo "$paths" | grep -c .) + local count=$(echo "$paths" | grep -c .) # If just one if [[ "$count" -eq 1 ]]; then @@ -60,7 +44,7 @@ function _sure() { if [[ $# -ne 1 ]]; then return 1 fi - prompt=$(echo "$1" | _ansi) + local prompt=$(echo "$1" | _ansi) while true; do read -p "$prompt [y/N] " -r if [[ "${REPLY,,}" =~ ^(y|yes)$ ]]; then diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index 556b6e3..e0be583 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -2,6 +2,7 @@ output=$(cat) +# touch foo.py && foo.py regex="bash: (.*\.py): command not found" if [[ "$output" =~ $regex ]]; then @@ -12,12 +13,14 @@ if [[ "$output" =~ $regex ]]; then fi fi +# mkdir foo && ./foo regex="bash: \./([^:]*): Is a directory" if [[ "$output" =~ $regex ]]; then - echo "Cannot execute a directory. Did you mean to \`cd\` into \`${BASH_REMATCH[1]}\` instead?" + echo "Cannot execute a directory. Did you mean to run \`cd ${BASH_REMATCH[1]}\`?" exit fi +# touch foo.c && ./foo.c regex="bash: \./(([^:]*)\.c): Permission denied" if [[ "$output" =~ $regex ]]; then @@ -28,7 +31,7 @@ if [[ "$output" =~ $regex ]]; then fi fi -regex="bash: (\./.*\.py): Permission denied" +regex="bash: \./(.*\.py): Permission denied" if [[ "$output" =~ $regex ]]; then # If file exists @@ -38,6 +41,7 @@ if [[ "$output" =~ $regex ]]; then fi fi +# touch foo && /.foo regex="bash: /\.([^:]*): No such file or directory" if [[ "$output" =~ $regex ]]; then diff --git a/opt/cs50/lib/help50/cd b/opt/cs50/lib/help50/cd index a9bf588..c451e02 100755 --- a/opt/cs50/lib/help50/cd +++ b/opt/cs50/lib/help50/cd @@ -1 +1,20 @@ #!/bin/bash + +. /opt/cs50/lib/cli + +output=$(cat) + +# mkdir -p foo/bar && cd bar +regex="cd: (.*): No such file or directory" +if [[ "$output" =~ $regex ]]; then + + # Search recursively for directory + dir="${BASH_REMATCH[1]}" + parent=$(_search "$dir") + echo -n "There isn't a directory called \`$dir\` in your current directory." + if [[ ! -z "$parent" ]]; then + echo " Did you mean to \`cd $parent\` first?" + else + echo + fi +fi From 29ddbfdf11223a1df267de984eb196c5b7cbede5 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 8 May 2024 19:57:21 -0400 Subject: [PATCH 48/76] removed Makefile check --- opt/cs50/bin/make | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/opt/cs50/bin/make b/opt/cs50/bin/make index ca6cbd7..1548007 100755 --- a/opt/cs50/bin/make +++ b/opt/cs50/bin/make @@ -3,18 +3,14 @@ # If a single target and not an option if [[ $# -eq 1 ]] && [[ "$1" != -* ]]; then - # If no Makefile - if [[ ! -f Makefile && ! -f makefile ]]; then + # If target ends with .c or is a directory + if [[ "$1" == *?.c || -d "$1" ]]; then - # If target ends with .c or is a directory - if [[ "$1" == *?.c || -d "$1" ]]; then + # Don't suppress "Nothing to be done" with --silent + /usr/bin/make "$1" - # Don't suppress "Nothing to be done" with --silent - /usr/bin/make "$1" - - # Else make exits with 0 - exit 1 - fi + # Else make exits with 0 + exit 1 fi fi From 2ec2530183efb278f4175519db207a4a16cc1350 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 8 May 2024 19:57:35 -0400 Subject: [PATCH 49/76] WIP8 --- etc/profile.d/help50.sh | 31 ++++++++++++++++--------------- opt/cs50/lib/cli | 15 ++++++++++----- opt/cs50/lib/help50/bash | 11 +++++++++++ opt/cs50/lib/help50/cd | 4 ++-- opt/cs50/lib/help50/make | 8 ++++---- opt/cs50/lib/help50/python | 2 +- 6 files changed, 44 insertions(+), 27 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 490e09f..c38c649 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -9,21 +9,6 @@ HELPERS="/opt/cs50/lib/help50" # Library . /opt/cs50/lib/cli -# Disable yes, lest users type it at prompt -function yes() { - if [[ -t 0 ]]; then - _alert "That was a rhetorical question. :)" - else - command yes - fi -} -alias y=yes - -# Don't override `n`, though -function no() { - _alert "That was a rhetorical question. :)" -} - # Ignore duplicates (but not commands that begin with spaces) export HISTCONTROL="ignoredups" @@ -38,6 +23,11 @@ function _help50() { local argv0=$(HISTFILE=$histfile history 1 | cut -c 8- | awk '{print $1}') rm --force $histfile + # Remove aliases + for name in n no y yes; do + unalias $name 2> /dev/null + done + # If last command erred (and is not ctl-c or ctl-z) # https://tldp.org/LDP/abs/html/exitcodes.html if [[ $status -ne 0 && $status -ne 130 && $status -ne 148 ]]; then @@ -98,9 +88,20 @@ function _help50() { truncate -s 0 /tmp/help50.$HELP50.typescript } +function _question() { + _alert "That was a rhetorical question. :)" +} + # Default helpers if ! type _helpful >/dev/null 2>&1; then function _helpful() { + + # Intercept accidental invocation of `yes` and `n`, which are actual programs + for name in n no y yes; do + alias $name=_question + done + + # Output help local output=$(_ansi "$1") _alert "$output" } diff --git a/opt/cs50/lib/cli b/opt/cs50/lib/cli index 0ea76be..a119ede 100644 --- a/opt/cs50/lib/cli +++ b/opt/cs50/lib/cli @@ -19,15 +19,20 @@ function _ansi() { echo "$input" | sed "s/\`\\([^\`]*\\)\`/${bold}\\1${normal}/g" } -function _search() { - - # In $1 is path to find - if [[ $# -ne 1 ]]; then +function _find() { + + # Usage + if [[ $# -eq 1 ]]; then # Files AND directories + local path="$1" + elif [[ $# -eq 3 && "$1" == "-type" && "$2" =~ ^[df]$ ]]; then # Files OR directories + local type="$1 $2" + local path="$3" + else return fi # Find any $1 in descendants of $WORKDIR - paths=$(find "$WORKDIR" -name "$1" -printf "%T+ %p\n" | sort -nr | awk '{print $2}' 2> /dev/null) + paths=$(find "$WORKDIR" -name "$path" -printf "%T+ %p\n" $type | sort -nr | awk '{print $2}' 2> /dev/null) # Count paths local count=$(echo "$paths" | grep -c .) diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index e0be583..4288552 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -13,6 +13,17 @@ if [[ "$output" =~ $regex ]]; then fi fi +# mkdir foo && bar +regex="bash: (.*): command not found" +if [[ "$output" =~ $regex ]]; then + + # If directory exists + if [[ -d "${BASH_REMATCH[1]}" ]]; then + echo "Did you mean to run \`cd ${BASH_REMATCH[1]}\`?" + exit + fi +fi + # mkdir foo && ./foo regex="bash: \./([^:]*): Is a directory" if [[ "$output" =~ $regex ]]; then diff --git a/opt/cs50/lib/help50/cd b/opt/cs50/lib/help50/cd index c451e02..0722752 100755 --- a/opt/cs50/lib/help50/cd +++ b/opt/cs50/lib/help50/cd @@ -10,10 +10,10 @@ if [[ "$output" =~ $regex ]]; then # Search recursively for directory dir="${BASH_REMATCH[1]}" - parent=$(_search "$dir") + parent=$(_find -type d "$dir") echo -n "There isn't a directory called \`$dir\` in your current directory." if [[ ! -z "$parent" ]]; then - echo " Did you mean to \`cd $parent\` first?" + echo " Did you mean to run \`cd $parent\` first?" else echo fi diff --git a/opt/cs50/lib/help50/make b/opt/cs50/lib/help50/make index f23c37e..347f51b 100755 --- a/opt/cs50/lib/help50/make +++ b/opt/cs50/lib/help50/make @@ -9,7 +9,7 @@ if [[ "$output" =~ $regex ]]; then # If target is a directory if [[ -d "${BASH_REMATCH[1]}" ]]; then - echo "Cannot run \`make\` on a directory. Did you mean to \`cd ${BASH_REMATCH[1]}\` first?" + echo "Cannot run \`make\` on a directory. Did you mean to run \`cd ${BASH_REMATCH[1]}\`?" exit fi @@ -17,7 +17,7 @@ if [[ "$output" =~ $regex ]]; then if [[ "${BASH_REMATCH[1]}" == *?.c ]]; then base="${BASH_REMATCH[1]%.c}" if [[ -n "$base" && ! -d "$base" ]]; then - echo "Did you mean to \`make ${base}\`?" + echo "Did you mean to run \`make ${base}\`?" exit fi fi @@ -32,10 +32,10 @@ if [[ "$output" =~ $regex ]]; then if [[ ! -f "$file" ]]; then # Search recursively for .c file - dir=$(_search "$file") + dir=$(_find -type f "$file") echo -n "There isn't a file called \`$file\` in your current directory." if [[ ! -z "$dir" ]]; then - echo " Did you mean to \`cd $dir\` first?" + echo " Did you mean to run \`cd $dir\` first?" else echo fi diff --git a/opt/cs50/lib/help50/python b/opt/cs50/lib/help50/python index 8248449..8bc7a30 100755 --- a/opt/cs50/lib/help50/python +++ b/opt/cs50/lib/help50/python @@ -28,7 +28,7 @@ if [[ "$output" =~ $regex ]]; then # If .py is in $PWD if [[ -n "$path" && "$path" == $(basename "$path") ]]; then - dir=$(_search "$path") + dir=$(_find -type f "$path") echo -n "There isn't a file called \`$path\` in your current directory." if [[ ! -z "$dir" ]]; then echo " Did you mean to \`cd $dir\` first?" From db691ca7a62d7bcaee0c274e477da6e4b80a2958 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 8 May 2024 21:20:39 -0400 Subject: [PATCH 50/76] WIP --- etc/profile.d/help50.sh | 7 ++++--- opt/cs50/lib/help50/bash | 6 ++++++ opt/cs50/lib/help50/clang | 14 ++++++++++++++ tests/bar.c | 1 - tests/baz.c | 0 tests/foo/baz.c | 0 tests/foo/foo2/foo3/x.c | 0 tests/foo/qux | 0 tests/foo/y.c | 0 tests/y.c | 0 10 files changed, 24 insertions(+), 4 deletions(-) create mode 100755 opt/cs50/lib/help50/clang delete mode 100644 tests/bar.c delete mode 100644 tests/baz.c delete mode 100644 tests/foo/baz.c delete mode 100644 tests/foo/foo2/foo3/x.c delete mode 100755 tests/foo/qux delete mode 100644 tests/foo/y.c delete mode 100644 tests/y.c diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index c38c649..a32ba2a 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -17,10 +17,10 @@ function _help50() { # Get exit status of last command local status=$? - # Get last command, independent of user's actual history + # Get last command line, independent of user's actual history histfile=/tmp/help50.$$.history HISTFILE=$histfile history -a - local argv0=$(HISTFILE=$histfile history 1 | cut -c 8- | awk '{print $1}') + local argv=$(HISTFILE=$histfile history 1 | cut -c 8-) # Could technically contain multiple commands, separated by ; or && rm --force $histfile # Remove aliases @@ -33,7 +33,8 @@ function _help50() { if [[ $status -ne 0 && $status -ne 130 && $status -ne 148 ]]; then # Ignore ./* if executable file - if [[ "$argv" =~ ^\./ && -f "$argv" && -x "$argv" ]]; then + local argv0=$(echo "$argv" | awk '{print $1}') # Assume for simplicity it's just a single command + if [[ "$argv0" =~ ^\./ && -f "$argv0" && -x "$argv0" ]]; then break fi diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index 4288552..61521b7 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -17,6 +17,12 @@ fi regex="bash: (.*): command not found" if [[ "$output" =~ $regex ]]; then + # If typo + if [[ "${BASH_REMATCH[1]}" == "1s" ]]; then + echo "Did you mean to run \`ls\` (which starts with a lowercase L)?" + exit + fi + # If directory exists if [[ -d "${BASH_REMATCH[1]}" ]]; then echo "Did you mean to run \`cd ${BASH_REMATCH[1]}\`?" diff --git a/opt/cs50/lib/help50/clang b/opt/cs50/lib/help50/clang new file mode 100755 index 0000000..8c7aa69 --- /dev/null +++ b/opt/cs50/lib/help50/clang @@ -0,0 +1,14 @@ +#!/bin/bash + +output=$(cat) + +# touch helpers.c && make helpers +regex="undefined reference to \`main'" +if [[ "$output" =~ $regex ]]; then + regex="make: \*\*\* \[: (.*)\] Error 1" + if [[ "$output" =~ $regex ]]; then + file="${BASH_REMATCH[1]}.c" + echo "Looks like \`$file\` does not have a \`main\` function. Did you mean to \`make\` something else?" + exit + fi +fi diff --git a/tests/bar.c b/tests/bar.c deleted file mode 100644 index b552c8e..0000000 --- a/tests/bar.c +++ /dev/null @@ -1 +0,0 @@ -int main(void) {} diff --git a/tests/baz.c b/tests/baz.c deleted file mode 100644 index e69de29..0000000 diff --git a/tests/foo/baz.c b/tests/foo/baz.c deleted file mode 100644 index e69de29..0000000 diff --git a/tests/foo/foo2/foo3/x.c b/tests/foo/foo2/foo3/x.c deleted file mode 100644 index e69de29..0000000 diff --git a/tests/foo/qux b/tests/foo/qux deleted file mode 100755 index e69de29..0000000 diff --git a/tests/foo/y.c b/tests/foo/y.c deleted file mode 100644 index e69de29..0000000 diff --git a/tests/y.c b/tests/y.c deleted file mode 100644 index e69de29..0000000 From 1b88177109c3a8f21457880e101d0bbe2f08e6d1 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 8 May 2024 21:22:29 -0400 Subject: [PATCH 51/76] WIP --- etc/profile.d/cli.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/etc/profile.d/cli.sh b/etc/profile.d/cli.sh index 9727517..29be1a1 100644 --- a/etc/profile.d/cli.sh +++ b/etc/profile.d/cli.sh @@ -61,8 +61,8 @@ if [ `id -u` -ne 0 ]; then # Valgrind export VALGRIND_OPTS="--memcheck:leak-check=full --memcheck:show-leak-kinds=all --memcheck:track-origins=yes" - # Start help50 if enabled - if help50 is-enabled > /dev/null; then - help50 start - fi + ## TODO: Start help50 if enabled + # if help50 is-enabled > /dev/null; then + # help50 start + # fi fi From 9e770456f9743ca6eaf6f326034351e1456dcb59 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 8 May 2024 23:35:05 -0400 Subject: [PATCH 52/76] removed TAG from Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 15f61ad..2d9b392 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ rebuild: docker build --build-arg VCS_REF=$(shell git rev-parse HEAD) --no-cache --tag $(IMAGE) . run: - docker run --env LANG=$(LANG) --env LOCAL_WORKSPACE_FOLDER="$(PWD)" --env WORKDIR=/mnt --interactive --publish-all --rm --security-opt seccomp=unconfined --tty --volume "$(PWD)":/mnt --volume /var/run/docker.sock:/var/run/docker-host.sock --workdir /mnt cs50/cli:$(TAG) bash --login || true + docker run --env LANG=$(LANG) --env LOCAL_WORKSPACE_FOLDER="$(PWD)" --env WORKDIR=/mnt --interactive --publish-all --rm --security-opt seccomp=unconfined --tty --volume "$(PWD)":/mnt --volume /var/run/docker.sock:/var/run/docker-host.sock --workdir /mnt cs50/cli bash --login || true squash: depends docker-squash --tag $(IMAGE) $(IMAGE) From 6aa50eec88ca76bd98ec8dfda7e4f4d1924a4231 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 9 May 2024 00:16:34 -0400 Subject: [PATCH 53/76] removed hardcoding of typescript path --- etc/profile.d/help50.sh | 6 +++--- opt/cs50/bin/help50 | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index a32ba2a..b4c3438 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -18,7 +18,7 @@ function _help50() { local status=$? # Get last command line, independent of user's actual history - histfile=/tmp/help50.$$.history + histfile=$(mktemp) HISTFILE=$histfile history -a local argv=$(HISTFILE=$histfile history 1 | cut -c 8-) # Could technically contain multiple commands, separated by ; or && rm --force $histfile @@ -39,7 +39,7 @@ function _help50() { fi # Read typescript from disk - local typescript=$(cat /tmp/help50.$HELP50.typescript) + local typescript=$(cat $HELP50) # Remove script's own output (if this is user's first command) typescript=$(echo "$typescript" | sed '1{/^Script started on .*/d}') @@ -86,7 +86,7 @@ function _help50() { fi # Truncate typescript - truncate -s 0 /tmp/help50.$HELP50.typescript + truncate -s 0 $HELP50 } function _question() { diff --git a/opt/cs50/bin/help50 b/opt/cs50/bin/help50 index 1120587..f6ca0fc 100755 --- a/opt/cs50/bin/help50 +++ b/opt/cs50/bin/help50 @@ -30,17 +30,17 @@ function _start() { return 0 fi - # PID of parent shell to help - local HELP50=$PPID + # Uniquely identify typescript using PID of parent shell to help + local HELP50=/tmp/help50.$PPID # Start `script` in background, using ; instead of && for command, # else if user logs out (as via ctl-d) after a non-0 command, bash exits with 127 set -o monitor - HELP50=$HELP50 script --append --command "bash --login ; exit 1" --flush --quiet --return /tmp/help50.$HELP50.typescript + HELP50=$HELP50 script --append --command "bash --login ; exit 1" --flush --quiet --return $HELP50 local status=$? # No longer helping - rm --force /tmp/help50.$HELP50.* + rm --force $HELP50 # If `script` was killed, in which case `exit 1` above won't execute if [[ $status -ne 1 ]]; then @@ -52,7 +52,7 @@ function _start() { else # Kill parent shell, since user presumably wants to exit - kill -SIGHUP $HELP50 + kill -SIGHUP $PPID fi } From 3eae3bde8a9075ff5273ce0c66b45c28a9f0f4cd Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 9 May 2024 14:57:03 -0400 Subject: [PATCH 54/76] omitting dot directories --- opt/cs50/lib/cli | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opt/cs50/lib/cli b/opt/cs50/lib/cli index a119ede..8705de1 100644 --- a/opt/cs50/lib/cli +++ b/opt/cs50/lib/cli @@ -31,8 +31,8 @@ function _find() { return fi - # Find any $1 in descendants of $WORKDIR - paths=$(find "$WORKDIR" -name "$path" -printf "%T+ %p\n" $type | sort -nr | awk '{print $2}' 2> /dev/null) + # Find $path in descendants of $WORKDIR, excluding hidden directories + paths=$(find "$WORKDIR" -not -path "*/.*" -name "$path" -printf "%T+ %p\n" $type | sort -nr | awk '{print $2}' 2> /dev/null) # Count paths local count=$(echo "$paths" | grep -c .) From e43789b74b11f6a6fb6328f0fb6ec83c534fca07 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 9 May 2024 14:57:13 -0400 Subject: [PATCH 55/76] added helpers --- opt/cs50/lib/help50/bash | 8 ++++++++ opt/cs50/lib/help50/cd | 7 +++++++ opt/cs50/lib/help50/make | 7 ++++--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index 61521b7..4bd0fa1 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -37,6 +37,14 @@ if [[ "$output" =~ $regex ]]; then exit fi +# touch foo && cd foo +regex="bash: cd: (.*): Not a directory" +if [[ "$output" =~ $regex ]]; then + file="${BASH_REMATCH[1]}" + echo "Looks like you're trying to change directories, but \`$file\` isn't a directory." + exit +fi + # touch foo.c && ./foo.c regex="bash: \./(([^:]*)\.c): Permission denied" if [[ "$output" =~ $regex ]]; then diff --git a/opt/cs50/lib/help50/cd b/opt/cs50/lib/help50/cd index 0722752..8fa9262 100755 --- a/opt/cs50/lib/help50/cd +++ b/opt/cs50/lib/help50/cd @@ -18,3 +18,10 @@ if [[ "$output" =~ $regex ]]; then echo fi fi + +# cd.. || cd. +regex="bash: cd\.\.?: command not found" +if [[ "$output" =~ $regex ]]; then + echo "Did you mean to run \`cd ..\` instead?" + exit +fi diff --git a/opt/cs50/lib/help50/make b/opt/cs50/lib/help50/make index 347f51b..db79c38 100755 --- a/opt/cs50/lib/help50/make +++ b/opt/cs50/lib/help50/make @@ -9,7 +9,7 @@ if [[ "$output" =~ $regex ]]; then # If target is a directory if [[ -d "${BASH_REMATCH[1]}" ]]; then - echo "Cannot run \`make\` on a directory. Did you mean to run \`cd ${BASH_REMATCH[1]}\`?" + echo "Cannot run \`make\` on a directory. Did you mean to run \`cd ${BASH_REMATCH[1]}\` instead?" exit fi @@ -17,7 +17,7 @@ if [[ "$output" =~ $regex ]]; then if [[ "${BASH_REMATCH[1]}" == *?.c ]]; then base="${BASH_REMATCH[1]%.c}" if [[ -n "$base" && ! -d "$base" ]]; then - echo "Did you mean to run \`make ${base}\`?" + echo "Did you mean to run \`make ${base}\` instead?" exit fi fi @@ -28,7 +28,8 @@ regex="make: \*\*\* No rule to make target '(.*)'" if [[ "$output" =~ $regex ]]; then # If no .c file for target - file="${BASH_REMATCH[1]}.c" + file="${BASH_REMATCH[1]}" + [[ "$file" == *.c ]] || file="$file.c" if [[ ! -f "$file" ]]; then # Search recursively for .c file From f2b776a977ac9e5217f52e5ad9e7112c85839553 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 10 May 2024 14:07:59 -0400 Subject: [PATCH 56/76] removed ls helper --- opt/cs50/lib/help50/ls | 1 - 1 file changed, 1 deletion(-) delete mode 100755 opt/cs50/lib/help50/ls diff --git a/opt/cs50/lib/help50/ls b/opt/cs50/lib/help50/ls deleted file mode 100755 index a9bf588..0000000 --- a/opt/cs50/lib/help50/ls +++ /dev/null @@ -1 +0,0 @@ -#!/bin/bash From 7a6c0516513f57e9c251fc1622d8beccecbc62cf Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 10 May 2024 14:12:50 -0400 Subject: [PATCH 57/76] handling lack of -type in _find --- opt/cs50/lib/cli | 1 + 1 file changed, 1 insertion(+) diff --git a/opt/cs50/lib/cli b/opt/cs50/lib/cli index 8705de1..14ee660 100644 --- a/opt/cs50/lib/cli +++ b/opt/cs50/lib/cli @@ -24,6 +24,7 @@ function _find() { # Usage if [[ $# -eq 1 ]]; then # Files AND directories local path="$1" + local type="" elif [[ $# -eq 3 && "$1" == "-type" && "$2" =~ ^[df]$ ]]; then # Files OR directories local type="$1 $2" local path="$3" From 7c3c7db27375b5f48c5dc42179d8782de4716882 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Fri, 10 May 2024 14:36:48 -0400 Subject: [PATCH 58/76] improved, documented Python helpers --- opt/cs50/lib/help50/python | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/opt/cs50/lib/help50/python b/opt/cs50/lib/help50/python index 8bc7a30..cc00030 100755 --- a/opt/cs50/lib/help50/python +++ b/opt/cs50/lib/help50/python @@ -4,7 +4,8 @@ output=$(cat) -regex="AttributeError: module 'cs50' has no attribute '(get_(float|int|string)|SQL)'$" +# touch cs50.py && python -c "import cs50; cs50.get_int" && python -c "from cs50 import get_int" +regex="AttributeError: module 'cs50' has no attribute '.*'|ImportError: cannot import name '.*' from 'cs50'" if [[ "$output" =~ $regex ]]; then if [[ -f cs50.py ]]; then echo "You have a file called \`cs50.py\` that is \"shadowing\" CS50's own. Best to rename that file with \`mv\`." @@ -12,14 +13,25 @@ if [[ "$output" =~ $regex ]]; then fi fi -regex='^\s*(import string\s*$|from\s+string\s+import\s+)' -if echo "$output" | grep -Pq "$regex"; then +# touch re.py && python -c "import re; re.search" && python -c "from re import search" +regex="AttributeError: module 're' has no attribute '.*'|ImportError: cannot import name '.*' from 're'" +if [[ "$output" =~ $regex ]]; then + if [[ -f re.py ]]; then + echo "You have a file called \`re.py\` that is \"shadowing\" Python's own. Best to rename that file with \`mv\`." + exit + fi +fi + +# touch re.py && python -c "import string; string.digits" && python -c "from string import digits" +regex="AttributeError: module 'string' has no attribute '.*'|ImportError: cannot import name '.*' from 'string'" +if [[ "$output" =~ $regex ]]; then if [[ -f string.py ]]; then echo "You have a file called \`string.py\` that is \"shadowing\" Python's own. Best to rename that file with \`mv\`." exit fi fi +# mkdir foo && touch foo/bar.py && python bar.py regex="python: can't open file '(.*\.py)': \[Errno 2\] No such file or directory" if [[ "$output" =~ $regex ]]; then From f5cffb7c90ad06c4a670b4d31e37a699c7a34b29 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sun, 12 May 2024 23:55:48 -0400 Subject: [PATCH 59/76] removed break from CLI library --- etc/profile.d/cli.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etc/profile.d/cli.sh b/etc/profile.d/cli.sh index 29be1a1..d321990 100644 --- a/etc/profile.d/cli.sh +++ b/etc/profile.d/cli.sh @@ -62,7 +62,7 @@ if [ `id -u` -ne 0 ]; then export VALGRIND_OPTS="--memcheck:leak-check=full --memcheck:show-leak-kinds=all --memcheck:track-origins=yes" ## TODO: Start help50 if enabled - # if help50 is-enabled > /dev/null; then - # help50 start - # fi + if help50 is-enabled > /dev/null; then + help50 start + fi fi From 42c2a858f84f51e3c113076eea0a921d634b7112 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sun, 12 May 2024 23:55:55 -0400 Subject: [PATCH 60/76] added _trap --- etc/profile.d/help50.sh | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index b4c3438..8971b4b 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -35,7 +35,7 @@ function _help50() { # Ignore ./* if executable file local argv0=$(echo "$argv" | awk '{print $1}') # Assume for simplicity it's just a single command if [[ "$argv0" =~ ^\./ && -f "$argv0" && -x "$argv0" ]]; then - break + return fi # Read typescript from disk @@ -112,3 +112,16 @@ if ! type _helpless >/dev/null 2>&1; then fi export PROMPT_COMMAND=_help50 + +# touch foo.c && touch foo && ./foo +function _trap() { + if [[ "$BASH_COMMAND" =~ ^\./(.*)$ ]]; then + local src="${BASH_REMATCH[1]}.c" + local dst="${BASH_REMATCH[1]}" + if [[ "$src" -nt "$dst" ]]; then + local output=$(_ansi "It looks like \`$src\` has changed. Did you mean to run \`make $dst\` again?") + _alert "$output" + fi + fi +} +trap _trap DEBUG From f6e2329958c436d790178c5227b98ed86d2e8e2f Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sat, 18 May 2024 11:00:37 -0700 Subject: [PATCH 61/76] added helper for "syntax error near unexpected token" --- opt/cs50/lib/help50/bash | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index 4bd0fa1..dcc2729 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -76,3 +76,10 @@ if [[ "$output" =~ $regex ]]; then exit fi fi + +# int main(void) || do { +regex="bash: syntax error near unexpected token \`.*'" +if [[ "$output" =~ $regex ]]; then + echo "Did you mean to type that in a file instead of your terminal window?" + exit +fi From 3192a87e5f5cc712b96f35d77362297ccd2cc4ba Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Mon, 24 Jun 2024 11:29:48 -0400 Subject: [PATCH 62/76] added detection of backslash --- opt/cs50/lib/help50/bash | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index dcc2729..796d8c6 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -13,20 +13,28 @@ if [[ "$output" =~ $regex ]]; then fi fi -# mkdir foo && bar +# mkdir foo && (foo || 1s || .\foo) regex="bash: (.*): command not found" if [[ "$output" =~ $regex ]]; then + # If directory exists + if [[ -d "${BASH_REMATCH[1]}" ]]; then + echo "Did you mean to run \`cd ${BASH_REMATCH[1]}\`?" + exit + fi + # If typo if [[ "${BASH_REMATCH[1]}" == "1s" ]]; then echo "Did you mean to run \`ls\` (which starts with a lowercase L)?" exit fi - # If directory exists - if [[ -d "${BASH_REMATCH[1]}" ]]; then - echo "Did you mean to run \`cd ${BASH_REMATCH[1]}\`?" - exit + # If backslash instead of forward slash + if [[ "${BASH_REMATCH[1]}" =~ ^\.(.*) ]]; then + if [[ -f "./${BASH_REMATCH[1]}" ]]; then + echo "Did you mean to run \`./${BASH_REMATCH[1]}\`, with a forward slash instead?" + exit + fi fi fi From aa21d92b56e17cb8577b8b6a9233bd4c61fa5005 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Tue, 2 Jul 2024 19:08:16 -0400 Subject: [PATCH 63/76] detecting miscapitalized commands --- opt/cs50/lib/help50/bash | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index 796d8c6..defd38b 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -29,6 +29,13 @@ if [[ "$output" =~ $regex ]]; then exit fi + # If uppercase + argv0="${BASH_REMATCH[1],,}" # Lowercase it + if command -v "$argv0" &> /dev/null; then + echo "Did you mean to run \`$argv0\`, in lowercase instead?" + exit + fi + # If backslash instead of forward slash if [[ "${BASH_REMATCH[1]}" =~ ^\.(.*) ]]; then if [[ -f "./${BASH_REMATCH[1]}" ]]; then From 52bce6759c9561777f71b2804bf3930f1df36a98 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Tue, 2 Jul 2024 22:39:45 -0400 Subject: [PATCH 64/76] passing $1, $2, etc. to helpers now --- etc/profile.d/help50.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 8971b4b..a805c1e 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -72,7 +72,7 @@ function _help50() { # Try to get help for helper in $HELPERS/*; do if [[ -f $helper && -x $helper ]]; then - local help=$($helper <<< "$typescript") + local help=$($helper $argv <<< "$typescript") if [[ -n "$help" ]]; then break fi From 1fdf4a5283aa38a2bd97d83e8758ea44ec51052f Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Tue, 2 Jul 2024 22:39:59 -0400 Subject: [PATCH 65/76] added helper for, e.g., "check 50" --- opt/cs50/lib/help50/bash | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index defd38b..4f748de 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -36,6 +36,12 @@ if [[ "$output" =~ $regex ]]; then exit fi + # If CS50 command + if [[ "${BASH_REMATCH[1]}" =~ ^(check|style|submit)$ && "$2" == "50" ]]; then + echo "Did you mean to run \`${BASH_REMATCH[1]}50\`, without a space, instead?" + exit + fi + # If backslash instead of forward slash if [[ "${BASH_REMATCH[1]}" =~ ^\.(.*) ]]; then if [[ -f "./${BASH_REMATCH[1]}" ]]; then From fb3c69dc08f10cda5f1b3ef8203fdff3e4692b4f Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sun, 11 Aug 2024 17:42:59 -0400 Subject: [PATCH 66/76] fixed _sure --- opt/cs50/lib/cli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opt/cs50/lib/cli b/opt/cs50/lib/cli index 14ee660..f757b99 100644 --- a/opt/cs50/lib/cli +++ b/opt/cs50/lib/cli @@ -55,7 +55,7 @@ function _sure() { read -p "$prompt [y/N] " -r if [[ "${REPLY,,}" =~ ^(y|yes)$ ]]; then return 0 - elif [[ "${REPLY,,}" =~ ^(n|no)$ ]]; then + else return 1 fi done From 6baca23e70930a5f1fc00496df12635131a87373 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sun, 11 Aug 2024 17:48:15 -0400 Subject: [PATCH 67/76] installing file command --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index b0415a4..d5b42dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -184,6 +184,7 @@ RUN apt update && \ dos2unix \ dnsutils `# For nslookup` \ expect `# For help50` \ + file `# For help50` \ fonts-noto-color-emoji `# For render50` \ gdb \ git \ From bbd48209eb1921dfafad5cef77f4e3569d5c3910 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sun, 11 Aug 2024 17:48:27 -0400 Subject: [PATCH 68/76] improved DEBUG trap --- etc/profile.d/help50.sh | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index a805c1e..99ff86c 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -23,7 +23,7 @@ function _help50() { local argv=$(HISTFILE=$histfile history 1 | cut -c 8-) # Could technically contain multiple commands, separated by ; or && rm --force $histfile - # Remove aliases + # Remove any of these aliases for name in n no y yes; do unalias $name 2> /dev/null done @@ -113,15 +113,21 @@ fi export PROMPT_COMMAND=_help50 -# touch foo.c && touch foo && ./foo function _trap() { + + # touch foo.c && make foo && touch foo.c && ./foo if [[ "$BASH_COMMAND" =~ ^\./(.*)$ ]]; then local src="${BASH_REMATCH[1]}.c" local dst="${BASH_REMATCH[1]}" - if [[ "$src" -nt "$dst" ]]; then - local output=$(_ansi "It looks like \`$src\` has changed. Did you mean to run \`make $dst\` again?") - _alert "$output" + if [[ -f "$src" && $(file --brief --mime-type "$src") == "text/x-c" ]]; then + if [[ -x "$dst" && $(file --brief --mime-type "$dst") == "application/x-pie-executable" ]]; then + _helpful "It looks like \`$src\` has changed. Did you mean to run \`make $dst\` again?" + fi fi fi } + +# If the command run by the DEBUG trap returns a non-zero value, the next command is skipped and not executed +shopt -s extdebug + trap _trap DEBUG From 84d7819d67f7b97043451307e99f85d84b50a003 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sun, 11 Aug 2024 17:48:45 -0400 Subject: [PATCH 69/76] added Python helper --- opt/cs50/lib/help50/python | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/opt/cs50/lib/help50/python b/opt/cs50/lib/help50/python index cc00030..1d8b3fd 100755 --- a/opt/cs50/lib/help50/python +++ b/opt/cs50/lib/help50/python @@ -31,14 +31,26 @@ if [[ "$output" =~ $regex ]]; then fi fi -# mkdir foo && touch foo/bar.py && python bar.py +# mkdir -p foo/bar && touch foo/bar/baz.py && python baz.py regex="python: can't open file '(.*\.py)': \[Errno 2\] No such file or directory" if [[ "$output" =~ $regex ]]; then # Relative path from $PWD path=$(realpath --relative-to=. "${BASH_REMATCH[1]}") - # If .py is in $PWD + # If command was `python baz.py` (i.e., without a dirname) + if [[ -n "$path" && "$path" == $(basename "$path") ]]; then + dir=$(_find -type f "$path") + echo -n "There isn't a file called \`$path\` in your current directory." + if [[ ! -z "$dir" ]]; then + echo " Did you mean to \`cd $dir\` first?" + else + echo + fi + exit + fi + + # If command was `python bar/baz.py` (i.e., with a dirname) if [[ -n "$path" && "$path" == $(basename "$path") ]]; then dir=$(_find -type f "$path") echo -n "There isn't a file called \`$path\` in your current directory." From 21148f633830d45fa138cf06ee5b439e910f7d34 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Mon, 12 Aug 2024 15:28:08 -0400 Subject: [PATCH 70/76] removed DEBUG trap --- etc/profile.d/help50.sh | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 99ff86c..6fb5079 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -22,19 +22,34 @@ function _help50() { HISTFILE=$histfile history -a local argv=$(HISTFILE=$histfile history 1 | cut -c 8-) # Could technically contain multiple commands, separated by ; or && rm --force $histfile + local argv0=$(echo "$argv" | awk '{print $1}') # Assume for simplicity it's just a single command # Remove any of these aliases for name in n no y yes; do unalias $name 2> /dev/null done + # If last command was ./* + # touch foo.c && make foo && touch foo.c && ./foo + if [[ "$argv" =~ ^\./(.*)$ ]]; then + local src="${BASH_REMATCH[1]}.c" + local dst="${BASH_REMATCH[1]}" + if [[ -f "$src" && $(file --brief --mime-type "$src") == "text/x-c" ]]; then + if [[ -x "$dst" && $(file --brief --mime-type "$dst") == "application/x-pie-executable" ]]; then + if [[ "$src" -nt "$dst" ]]; then + _helpful "It looks like \`$src\` has changed. Did you mean to run \`make $dst\` again?" + fi + fi + fi + fi + # If last command erred (and is not ctl-c or ctl-z) # https://tldp.org/LDP/abs/html/exitcodes.html if [[ $status -ne 0 && $status -ne 130 && $status -ne 148 ]]; then # Ignore ./* if executable file - local argv0=$(echo "$argv" | awk '{print $1}') # Assume for simplicity it's just a single command if [[ "$argv0" =~ ^\./ && -f "$argv0" && -x "$argv0" ]]; then + echo XXX return fi @@ -72,6 +87,7 @@ function _help50() { # Try to get help for helper in $HELPERS/*; do if [[ -f $helper && -x $helper ]]; then + echo "[$helper]" local help=$($helper $argv <<< "$typescript") if [[ -n "$help" ]]; then break @@ -112,22 +128,3 @@ if ! type _helpless >/dev/null 2>&1; then fi export PROMPT_COMMAND=_help50 - -function _trap() { - - # touch foo.c && make foo && touch foo.c && ./foo - if [[ "$BASH_COMMAND" =~ ^\./(.*)$ ]]; then - local src="${BASH_REMATCH[1]}.c" - local dst="${BASH_REMATCH[1]}" - if [[ -f "$src" && $(file --brief --mime-type "$src") == "text/x-c" ]]; then - if [[ -x "$dst" && $(file --brief --mime-type "$dst") == "application/x-pie-executable" ]]; then - _helpful "It looks like \`$src\` has changed. Did you mean to run \`make $dst\` again?" - fi - fi - fi -} - -# If the command run by the DEBUG trap returns a non-zero value, the next command is skipped and not executed -shopt -s extdebug - -trap _trap DEBUG From 50b0adc38676130f7ed1703eb36336610d1b70bf Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Mon, 12 Aug 2024 15:28:44 -0400 Subject: [PATCH 71/76] removed check for executable in ./ --- etc/profile.d/help50.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 6fb5079..84b99af 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -47,12 +47,6 @@ function _help50() { # https://tldp.org/LDP/abs/html/exitcodes.html if [[ $status -ne 0 && $status -ne 130 && $status -ne 148 ]]; then - # Ignore ./* if executable file - if [[ "$argv0" =~ ^\./ && -f "$argv0" && -x "$argv0" ]]; then - echo XXX - return - fi - # Read typescript from disk local typescript=$(cat $HELP50) From 3f09407c90f487081786df699212d3e08bc7b5a7 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Wed, 14 Aug 2024 14:44:05 -0400 Subject: [PATCH 72/76] added _fold --- opt/cs50/lib/cli | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/opt/cs50/lib/cli b/opt/cs50/lib/cli index f757b99..dcba5e3 100644 --- a/opt/cs50/lib/cli +++ b/opt/cs50/lib/cli @@ -16,7 +16,7 @@ function _ansi() { # Format backticks as bold local bold=$(printf '\033[1m') local normal=$(printf '\033[22m') - echo "$input" | sed "s/\`\\([^\`]*\\)\`/${bold}\\1${normal}/g" + echo "$input" | sed "s/\`\\([^\`]*\\)\`/${bold}\\1${normal}/g" | _fold } function _find() { @@ -46,6 +46,22 @@ function _find() { fi } +function _fold() { + + # If command-line arguments + if [[ -t 0 ]]; then + input="$*" + + # If standard input + else + input=$(cat) + fi + + # Wrap long lines + local cols=$(tput cols) + echo "$input" | fold --spaces --width=$cols +} + function _sure() { if [[ $# -ne 1 ]]; then return 1 From 56d8fb7af466a2113772772cb9932eecbbeb32b7 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Thu, 15 Aug 2024 11:29:55 -0400 Subject: [PATCH 73/76] style --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d5b42dd..eaad0d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build stage -FROM ubuntu:24.04 as builder +FROM ubuntu:24.04 AS builder # Build-time variables From 3e9ca9e4965017ab28731bf97092f923fe23ed74 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sat, 5 Oct 2024 18:45:21 -0400 Subject: [PATCH 74/76] WIP --- Dockerfile | 1 + etc/profile.d/help50.sh | 3 +-- opt/cs50/lib/cli | 2 +- opt/cs50/lib/help50/bash | 14 +++++++++++--- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index eaad0d2..e87539b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -186,6 +186,7 @@ RUN apt update && \ expect `# For help50` \ file `# For help50` \ fonts-noto-color-emoji `# For render50` \ + fzf `# For help50` \ gdb \ git \ git-lfs \ diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 84b99af..65a63e5 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -81,7 +81,6 @@ function _help50() { # Try to get help for helper in $HELPERS/*; do if [[ -f $helper && -x $helper ]]; then - echo "[$helper]" local help=$($helper $argv <<< "$typescript") if [[ -n "$help" ]]; then break @@ -100,7 +99,7 @@ function _help50() { } function _question() { - _alert "That was a rhetorical question. :)" + _alert "That was a rhetorical question. <3" } # Default helpers diff --git a/opt/cs50/lib/cli b/opt/cs50/lib/cli index dcba5e3..9db45f2 100644 --- a/opt/cs50/lib/cli +++ b/opt/cs50/lib/cli @@ -32,7 +32,7 @@ function _find() { return fi - # Find $path in descendants of $WORKDIR, excluding hidden directories + # Find $path in descendants of $WORKDIR, excluding hidden directories, most recently modified first paths=$(find "$WORKDIR" -not -path "*/.*" -name "$path" -printf "%T+ %p\n" $type | sort -nr | awk '{print $2}' 2> /dev/null) # Count paths diff --git a/opt/cs50/lib/help50/bash b/opt/cs50/lib/help50/bash index 4f748de..bf00e0b 100755 --- a/opt/cs50/lib/help50/bash +++ b/opt/cs50/lib/help50/bash @@ -67,7 +67,7 @@ if [[ "$output" =~ $regex ]]; then fi # touch foo.c && ./foo.c -regex="bash: \./(([^:]*)\.c): Permission denied" +regex="bash: \./((.*)\.c): Permission denied" if [[ "$output" =~ $regex ]]; then # If file exists @@ -77,6 +77,7 @@ if [[ "$output" =~ $regex ]]; then fi fi +# touch foo.py && ./foo.py regex="bash: \./(.*\.py): Permission denied" if [[ "$output" =~ $regex ]]; then @@ -87,11 +88,18 @@ if [[ "$output" =~ $regex ]]; then fi fi +# echo "int main(void) {}" > foo && ./foo +regex="bash: \./([^\.]*): Permission denied" +if [[ "$output" =~ $regex ]]; then + if [[ $(file --brief --mime-type "${BASH_REMATCH[1]}") == "text/x-c" ]]; then + echo "Did you mean to give \`"${BASH_REMATCH[1]}"\` a name of \`"${BASH_REMATCH[1]}".c\` (and then compile it with \`make\`) instead?" + exit + fi +fi + # touch foo && /.foo regex="bash: /\.([^:]*): No such file or directory" if [[ "$output" =~ $regex ]]; then - - # If file exists if [[ -f "${BASH_REMATCH[1]}" ]]; then echo "Did you mean to run \`./${BASH_REMATCH[1]}\`?" exit From 8b8ae7830a294b6d337f1e7b9318ac5cb8789f54 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sat, 5 Oct 2024 19:50:15 -0400 Subject: [PATCH 75/76] WIP --- etc/profile.d/cli.sh | 2 +- etc/profile.d/help50.sh | 9 +++++++-- opt/cs50/bin/help50 | 15 ++++++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/etc/profile.d/cli.sh b/etc/profile.d/cli.sh index d321990..9727517 100644 --- a/etc/profile.d/cli.sh +++ b/etc/profile.d/cli.sh @@ -61,7 +61,7 @@ if [ `id -u` -ne 0 ]; then # Valgrind export VALGRIND_OPTS="--memcheck:leak-check=full --memcheck:show-leak-kinds=all --memcheck:track-origins=yes" - ## TODO: Start help50 if enabled + # Start help50 if enabled if help50 is-enabled > /dev/null; then help50 start fi diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 65a63e5..6e801be 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -92,13 +92,15 @@ function _help50() { elif [[ $status -ne 0 ]]; then # If helpless _helpless "$typescript" fi + else + _helped fi # Truncate typescript truncate -s 0 $HELP50 } -function _question() { +function _rhetorical() { _alert "That was a rhetorical question. <3" } @@ -108,7 +110,7 @@ if ! type _helpful >/dev/null 2>&1; then # Intercept accidental invocation of `yes` and `n`, which are actual programs for name in n no y yes; do - alias $name=_question + alias $name=_rhetocial done # Output help @@ -116,6 +118,9 @@ if ! type _helpful >/dev/null 2>&1; then _alert "$output" } fi +if ! type _helped >/dev/null 2>&1; then + function _helped() { :; } # Silent +fi if ! type _helpless >/dev/null 2>&1; then function _helpless() { :; } # Silent fi diff --git a/opt/cs50/bin/help50 b/opt/cs50/bin/help50 index f6ca0fc..7b88dc5 100755 --- a/opt/cs50/bin/help50 +++ b/opt/cs50/bin/help50 @@ -56,6 +56,16 @@ function _start() { fi } +function _status() { + if [[ -n "$HELP50" ]]; then + echo started + return 0 + else + echo stopped + return 1 + fi +} + function _stop() { # If not helping @@ -84,11 +94,14 @@ case "$1" in start) _start ;; + status) + _status + ;; stop) _stop ;; *) - echo "Usage: $0 [disable|enable|is-enabled|start|stop]" + echo "Usage: $0 [disable|enable|is-enabled|start|status|stop]" exit 1 ;; esac From c6fe70f7804a9878ec1013bc342a10f1cb7ecf39 Mon Sep 17 00:00:00 2001 From: "David J. Malan" Date: Sat, 5 Oct 2024 20:12:27 -0400 Subject: [PATCH 76/76] WIP --- etc/profile.d/help50.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etc/profile.d/help50.sh b/etc/profile.d/help50.sh index 6e801be..04dca38 100644 --- a/etc/profile.d/help50.sh +++ b/etc/profile.d/help50.sh @@ -105,6 +105,9 @@ function _rhetorical() { } # Default helpers +if ! type _helped >/dev/null 2>&1; then + function _helped() { :; } # Silent +fi if ! type _helpful >/dev/null 2>&1; then function _helpful() { @@ -118,9 +121,6 @@ if ! type _helpful >/dev/null 2>&1; then _alert "$output" } fi -if ! type _helped >/dev/null 2>&1; then - function _helped() { :; } # Silent -fi if ! type _helpless >/dev/null 2>&1; then function _helpless() { :; } # Silent fi