Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds new version of help50 #210

Open
wants to merge 83 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
e7781bf
added helpers for make
dmalan Jan 5, 2024
1f25bf5
tidying helper framework
dmalan Jan 5, 2024
f92afe5
using functions instead of aliases
dmalan Jan 6, 2024
5470fcf
removed support for exit statuses, supporting any executable helper
dmalan Jan 6, 2024
c4b6fbe
added PIPESTATUS for helpers
dmalan Jan 6, 2024
8ee1e52
removed PIPESTATUS
dmalan Jan 6, 2024
b003947
removed test file
dmalan Jan 6, 2024
9279b1d
added _helpful, _helpless
dmalan Jan 6, 2024
26e431d
removed, updated wrappers
dmalan Jan 6, 2024
a0783ce
updated formatting
dmalan Jan 6, 2024
cadd890
added _help function to standardize formatting
dmalan Jan 6, 2024
3041bf9
added on/off
dmalan Jan 6, 2024
a3c55b3
added icon to _help
dmalan Jan 9, 2024
7f315ae
WIP
dmalan Jan 17, 2024
5c70d23
WIP
dmalan Jan 17, 2024
36c860c
WIP
dmalan Jan 18, 2024
22a873b
WIP
dmalan Jan 18, 2024
bbaa372
WIP
dmalan Jan 18, 2024
b1b2360
checking for ctl-z
dmalan Jan 18, 2024
bd215bc
checking parent dirs
dmalan Jan 18, 2024
a69df22
added _find helper
dmalan Jan 18, 2024
ebaec05
renamed helper
dmalan Jan 18, 2024
bba678c
added helper
dmalan Jan 18, 2024
1a38952
added helper
dmalan Jan 18, 2024
501a59d
added helpers
dmalan Jan 19, 2024
d96a3d5
Merge pull request #201 from cs50/main
dmalan Jan 27, 2024
bc67d10
Merge branch 'main' into help50
dmalan Jan 31, 2024
383f4a2
tidied formatting, filesystem
dmalan Jan 31, 2024
72c3d53
capping size of help50 input
dmalan Jan 31, 2024
eb6647d
improved non-greedy matching
dmalan Feb 2, 2024
26facb7
added helper for .c
dmalan Feb 2, 2024
45cb7f3
use arm64 github runner for building arm image
rongxin-liu Apr 28, 2024
372761f
build amd64 and arm64 docker images concurrently
rongxin-liu Apr 28, 2024
b9a5ee3
create canary build
rongxin-liu Apr 28, 2024
2919d87
updated workflow actions versions
rongxin-liu Apr 28, 2024
f5b22c4
added comments
rongxin-liu Apr 28, 2024
d3a6a9c
Merge pull request #209 from cs50/arm64-runner
rongxin-liu Apr 28, 2024
fe13454
updated actions/github-script to version v7
rongxin-liu May 2, 2024
8556c53
WIP
dmalan May 6, 2024
5f11fef
WIP
dmalan May 6, 2024
a91969f
WIP2
dmalan May 7, 2024
73601b9
WIP2
dmalan May 7, 2024
d6fc53a
WIP3
dmalan May 7, 2024
0c96044
WIP4
dmalan May 7, 2024
72dd67f
WIP5
dmalan May 8, 2024
d5a938c
WIP6
dmalan May 8, 2024
f96d245
WIP7
dmalan May 8, 2024
4b19fc4
added WORKDIR
dmalan May 8, 2024
2801456
WIP7
dmalan May 8, 2024
35dd708
WIP8
dmalan May 8, 2024
29ddbfd
removed Makefile check
dmalan May 8, 2024
2ec2530
WIP8
dmalan May 8, 2024
db691ca
WIP
dmalan May 9, 2024
1b88177
WIP
dmalan May 9, 2024
16154b3
Merge branch 'main' into help50
dmalan May 9, 2024
9e77045
removed TAG from Makefile
dmalan May 9, 2024
6aa50ee
removed hardcoding of typescript path
dmalan May 9, 2024
3eae3bd
omitting dot directories
dmalan May 9, 2024
e43789b
added helpers
dmalan May 9, 2024
f2b776a
removed ls helper
dmalan May 10, 2024
7a6c051
handling lack of -type in _find
dmalan May 10, 2024
7c3c7db
improved, documented Python helpers
dmalan May 10, 2024
f5cffb7
removed break from CLI library
dmalan May 13, 2024
42c2a85
added _trap
dmalan May 13, 2024
f6e2329
added helper for "syntax error near unexpected token"
dmalan May 18, 2024
2051c45
merged in main
dmalan Jun 24, 2024
3192a87
added detection of backslash
dmalan Jun 24, 2024
aa21d92
detecting miscapitalized commands
dmalan Jul 2, 2024
52bce67
passing $1, $2, etc. to helpers now
dmalan Jul 3, 2024
1fdf4a5
added helper for, e.g., "check 50"
dmalan Jul 3, 2024
fb3c69d
fixed _sure
dmalan Aug 11, 2024
6baca23
installing file command
dmalan Aug 11, 2024
bbd4820
improved DEBUG trap
dmalan Aug 11, 2024
84d7819
added Python helper
dmalan Aug 11, 2024
21148f6
removed DEBUG trap
dmalan Aug 12, 2024
50b0adc
removed check for executable in ./
dmalan Aug 12, 2024
3f09407
added _fold
dmalan Aug 14, 2024
56d8fb7
style
dmalan Aug 15, 2024
3e9ca9e
WIP
dmalan Oct 5, 2024
852ad29
Merge branch 'main' into help50
dmalan Oct 5, 2024
be19810
merged
dmalan Oct 5, 2024
8b8ae78
WIP
dmalan Oct 5, 2024
c6fe70f
WIP
dmalan Oct 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,16 @@ RUN apt update && \
ca-certificates \
clang \
clang-format \
colorized-logs `# For help50` \
coreutils `# For fold` \
cowsay \
curl \
dos2unix \
dnsutils `# For nslookup` \
expect `# For help50` \
file `# For help50` \
fonts-noto-color-emoji `# For render50` \
fzf `# For help50` \
gdb \
git \
git-lfs \
Expand Down Expand Up @@ -253,7 +257,6 @@ RUN pip3 install --no-cache-dir \
cs50 \
Flask \
Flask-Session \
help50 \
pytest \
render50 \
setuptools \
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)" --interactive --publish-all --rm --security-opt seccomp=unconfined --tty --volume "$(PWD)":/mnt --volume /var/run/docker.sock:/var/run/docker-host.sock --workdir /mnt $(IMAGE) 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)
7 changes: 6 additions & 1 deletion etc/profile.d/cli.sh
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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
128 changes: 128 additions & 0 deletions etc/profile.d/help50.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# If not started
if [[ -z "$HELP50" ]]; then
return
fi

# Directory with helpers
HELPERS="/opt/cs50/lib/help50"

# Library
. /opt/cs50/lib/cli

# 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 line, independent of user's actual 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
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

# Read typescript from disk
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}')

# 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 $argv <<< "$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
else
_helped
fi

# Truncate typescript
truncate -s 0 $HELP50
}

function _rhetorical() {
_alert "That was a rhetorical question. <3"
}

# Default helpers
if ! type _helped >/dev/null 2>&1; then
function _helped() { :; } # Silent
fi
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=_rhetocial
done

# Output help
local output=$(_ansi "$1")
_alert "$output"
}
fi
if ! type _helpless >/dev/null 2>&1; then
function _helpless() { :; } # Silent
fi

export PROMPT_COMMAND=_help50
107 changes: 107 additions & 0 deletions opt/cs50/bin/help50
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/bin/bash

# If root
if [[ `id -u` -eq 0 ]]; then
exit 1
fi

function _disable() {
touch /tmp/help50.lock
}

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
return 0
fi

# 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 $HELP50
local status=$?

# No longer helping
rm --force $HELP50

# If `script` was killed, in which case `exit 1` above won't execute
if [[ $status -ne 1 ]]; then

# Without this, prompt ends up below and to right of "Session terminated, killing shell... ...killed."
echo -e "\r"

# 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 $PPID
fi
}

function _status() {
if [[ -n "$HELP50" ]]; then
echo started
return 0
else
echo stopped
return 1
fi
}

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
kill -SIGTERM $ggppid
}

# Parse argument
case "$1" in
disable)
_disable
;;
enable)
_enable
;;
is-enabled)
_is-enabled
;;
start)
_start
;;
status)
_status
;;
stop)
_stop
;;
*)
echo "Usage: $0 [disable|enable|is-enabled|start|status|stop]"
exit 1
;;
esac
12 changes: 4 additions & 8 deletions opt/cs50/bin/http-server
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/bash

. /opt/cs50/lib/cli

# Default options
a="-a 0.0.0.0"
c="-c-1"
Expand All @@ -9,22 +11,16 @@ port="-p 8080"
options="--no-dotfiles"
t="-t0"

# Formatting
bold=$(tput bold)
normal=$(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
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 "Are you sure you want to serve ${bold}${1}${normal} 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
Expand Down
33 changes: 14 additions & 19 deletions opt/cs50/bin/make
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
#!/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 target ends with .c or is a directory
if [[ "$1" == *?.c || -d "$1" ]]; then

# Run make
if [[ -d "$1" ]]; then
echo "$1 is a directory"
exit 1
else
/usr/bin/make -B -s $*
# Don't suppress "Nothing to be done" with --silent
/usr/bin/make "$1"

# Else make exits with 0
exit 1
fi
fi

# Don't echo recipes
/usr/bin/make --always-make --silent "$@"
13 changes: 4 additions & 9 deletions opt/cs50/bin/sqlite3
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#!/bin/bash

# Formatting
bold=$(tput bold)
normal=$(tput sgr0)
. /opt/cs50/lib/cli

# If data is coming from stdin (pipe or redirection)
if [[ -p /dev/stdin || ! -t 0 ]]; then
Expand All @@ -12,22 +10,19 @@ 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
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

# If one command-line argument
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
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 "Are you sure you want to create ${bold}$1${normal}? [y/N] " -r
if [[ ! "${REPLY,,}" =~ ^y|yes$ ]]; then
if ! _sure "Are you sure you want to create \`$1\`?"; then
exit 1
fi
fi
Expand Down
Loading