Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
dmalan committed Jan 18, 2024
1 parent 5c70d23 commit 36c860c
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 86 deletions.
157 changes: 78 additions & 79 deletions etc/profile.d/help50.sh
Original file line number Diff line number Diff line change
@@ -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}"
102 changes: 102 additions & 0 deletions etc/profile.d/help50.sh.old
Original file line number Diff line number Diff line change
@@ -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
4 changes: 3 additions & 1 deletion opt/cs50/bin/make
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions opt/cs50/lib/help50/bash
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 4 additions & 4 deletions opt/cs50/lib/help50/make
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 36c860c

Please sign in to comment.