Skip to content

Instantly share code, notes, and snippets.

@alexpatel
Created March 12, 2017 16:34
Show Gist options
  • Save alexpatel/5d8086af23d4af90f0487aa8cf937ef8 to your computer and use it in GitHub Desktop.
Save alexpatel/5d8086af23d4af90f0487aa8cf937ef8 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
# Submit programming assignment submissions to the MOSS plagiarism detection
# server.
if [ $BASH_VERSINFO -lt 4 ];
then
echo "Expected bash 4.0.0 or later, found $BASH_VERSION"
exit 1
fi
shopt -u dotglob
NORM=`tput sgr0`
BOLD=`tput bold`
SCRIPT=`basename "$0"`
HOST=moss.stanford.edu
PORT=7690
USERID=
ASSN=
REPOS=
declare -A MOSSOPTS=( [l]=c [m]=10 [d]=0 [x]=0 [n]=250 )
function pushd { command pushd "$@" > /dev/null; }
function popd { command popd "$@" > /dev/null; }
# Print usage string
function USAGE {
cat <<EOF
Usage: $SCRIPT -a ASSN -u USERID -d REPOS [-s HOST] [-p PORT] [-m REPEAT]
[-n MATCHES] [-x EXPERIMENTAL]
${BOLD}-a${NORM} -- CS61 assignment number (1- 6)
${BOLD}-d${NORM} -- directory containing course submission repositories
${BOLD}-u${NORM} -- MOSS user ID
${BOLD}-s${NORM} -- MOSS server hostname (default: ${HOST})
${BOLD}-p${NORM} -- MOSS server port (default: ${PORT})
${BOLD}-m${NORM} -- maximum times a passage may appear before being ignored default: ${MOSSOPTS[m]})
${BOLD}-n${NORM} -- number of matching files to show in results (default: ${MOSSOPTS[n]})
${BOLD}-x${NORM} -- use experimental server (default: ${MOSSOPTS[d]})
${BOLD}-h${NORM} -- display this message
EOF
exit 0
}
# Parse command-line options
function PARSEOPTS {
while getopts :a:d:u:s:p:m:n:x:h OPT; do
case $OPT in
a) ASSN="pset${OPTARG}"
;;
d) REPOS="${OPTARG}"
;;
u) USERID="${OPTARG}"
;;
s) HOST="${OPTARG}"
;;
p) PORT=${OPTARG}
;;
m) MOSSOPTS[m]=${OPTARG}
;;
n) MOSSOPTS[n]=${OPTARG}
;;
x) MOSSOPTS[x]=${OPTARG}
;;
h) USAGE
;;
\?) echo -e \\n"Unknown option ${BOLD}$OPTARG${NORM}." && USAGE ;;
esac
done
if [ $OPTIND -eq 1 ]; then USAGE; fi
if [ -z "${ASSN}" ]; then echo "-${BOLD}a${NORM} is required." && USAGE; fi
if [ -z "${USERID}" ]; then echo "-${BOLD}u${NORM} is required." && USAGE; fi
if [ -z "${REPOS}" ]; then echo "-${BOLD}d${NORM} is required." && USAGE; fi
shift $((OPTIND-1))
}
# Test TCP connection to MOSS server
function TESTCONN {
nc -z -w5 $HOST $PORT > /dev/null
if [ $? -ne 0 ]; then echo "Connection failed: $HOST:$PORT" && exit 1; fi
}
# Open TCP connection to MOSS server
function SOCKOPEN { exec 3<>/dev/tcp/$HOST/$PORT; TESTCONN; }
# Close TCP connection to MOSS server
function SOCKCLOSE { TESTCONN; exec 3<&- && exec 3>&-; }
# Read response from MOSS server
function READRESP {
for lno in $(seq 1 2); do
read resp
echo $resp
if [ $lno -eq 1 ] && [ "$resp" != "yes"]; then
echo "Invalid language: ${MOSSOPTS[l]}" && exit 1
else
echo "Valid language: ${MOSSOPTS[l]}"
fi
if [ $lno -eq 2 ]; then echo "URL: $resp" && exit 0; fi
done < <(cat <&3)
}
# Read submissions query metadata to MOSS connection
function WRITEMETA {
echo -ne "moss $USERID\n" >&3
echo -ne "directory ${MOSSOPTS[d]}\n" >&3
echo -ne "X ${MOSSOPTS[x]}\n" >&3
echo -ne "maxmatches ${MOSSOPTS[m]}\n" >&3
echo -ne "show ${MOSSOPTS[n]}\n" >&3
echo -ne "language ${MOSSOPTS[l]}\n" >&3
}
# Write query suffix and submit command to MOSS connection
function WRITEQUERY {
echo -ne "query 0 $ASSN-$(date +%s)\n" >&3
echo -ne "end" >&3
}
# Write contents + metadata of a single file to MOSS connection
# Arguments: (file name, Git remote name, file index)
function WRITEFILE {
file=$1
remote=$2
filendx=$3
echo -e "\tWriting file: $remote/$file"
fname="${remote}_${file//[[:space:]]}"
echo -ne "file ${filendx} ${MOSSOPTS[l]} $(stat -f "%z" $file) $fname\n" >&3
echo -ne "$(cat $file)\n" >&3
}
# Parse student submissions Git repositories and submit all contained .c/.h
# files
function WRITEREPOS {
# external counter to avoid sub-process scope
filectr=/tmp/filectr
echo 1 > $filectr
pushd $REPOS
find * -prune -type d | while read repo; do
pushd $repo
git branch --all -r | tr -d ' ' | grep "master$" |\
while IFS='/' read remote branch; do
git reset --hard -q handout/master > /dev/null
if [[ $remote =~ "repo" ]]; then
git checkout -q $remote/$branch > /dev/null
ASSNDIR=$(find . -type d -name "${ASSN}" -print)
if [ -z $ASSNDIR ]; then
echo "$repo/$remote - could not find Assignment $ASSN"
else
pushd $ASSNDIR
nfiles=$(ls *.{c,h} | wc -l | tr -d ' ')
echo "${BOLD}Processing repository: $repo/$remote with $nfiles code files.${NORM}"
for file in $(ls *.{c,h}); do
filendx=$(cat $filectr)
WRITEFILE $file $remote $filendx
echo $[$(cat $filectr) + 1] > $filectr
done
git reset --hard -q handout/master > /dev/null
popd
fi
fi
done
popd
done
rm $filectr
}
# Write all code files for this assignment from handout respository
function WRITEBASE {
remote=handout
pushd $REPOS
pushd $(find * -prune -type d | head -n 1)
git checkout -q handout/master
ASSNDIR=$(find . -type d -name "${ASSN}" -print)
pushd $ASSNDIR
for file in $(ls *.{c,h}); do
WRITEFILE $file $remote 0
done
popd
popd
popd
}
function MAIN {
PARSEOPTS "$@"
echo "Opening MOSS socket connection."
SOCKOPEN
echo "Writing submission metadata."
WRITEMETA
echo "Writing common submission files."
WRITEBASE
echo "Writing student submission files."
WRITEREPOS
echo "Submitting MOSS query."
WRITEQUERY
READRESP
echo "Closing MOSS socket connection."
SOCKCLOSE
}
MAIN "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment