#!/usr/bin/python3

"""The ok.py script runs tests, checks for updates, and saves your work.

Common uses:
  python3 ok.py        Run unlocked tests, check for updates, and save work.
  python3 ok.py -u     Unlock new tests interactively.
  python3 ok.py -h     Display full help documentation.

This script will search the current directory for assignments. Make sure that
ok.py appears in the same directory as the assignment you wish to test.
"""

# TODO(denero) Add mechanism for removing DEVELOPER INSTRUCTIONS.
"""DEVELOPER INSTRUCTIONS

This multi-line string contains instructions for developers. It is removed
when the client is distributed to students.

This file is responsible for coordinating all communication with the ok-server.
Students should never need to modify this file.

Local and remote interactions are encapsulated as protocols.
Contributors should do the following to add a protocol to this autograder:

    1- Extend the Protocol class and implement on_start and on_interact.
    2- Add the classname of your protocol to the "protocols" list.
    3- If your protocol needs command line arguments, change parse_input.

A standard protocol lifecycle has only one round-trip communication with the
server, processed by on_start and on_response methods. If other interactions
are required outside of this lifecycle, the send_to_server function can be used
to send and receive information from the server outside of the default times.
Such communications should be limited to the body of an on_interact method.
"""
import argparse


class Protocol(object):
    """TODO(denero) Describe protocols, once we actually know what they do."""
    name = None

    def __init__(self, cmd_line_args):
        self.args = cmd_line_args # A dictionary of parsed arguments.

    def on_start(self):
        """Called when ok.py starts. Returns an object to be sent to server."""

    def on_response(self, response):
        """Called when ok-server responds with the server response."""

    def on_interact(self):
        """Called to execute an interactive or output-intensive session."""


class FileContents(Protocol):
    """The contents of changed source files are sent to the server."""
    name = 'file_contents'

    def on_start(self):
        """Find all source files and return their complete contents."""


class RunTests(Protocol):
    """Runs tests, formats results, and sends results to the server."""
    name = 'run_tests'

    def on_interact(self):
        """Run unlocked tests and print results."""


def send_to_server(messages):
    """Send messages to server, along with user authentication."""
    # TODO(denero) Send an {access_token, assignment, messages} post.


# TODO(denero) Pass around a buffer that checks for message duplication, rather
#              than using a global variable with shared access.
INPUT_BUFFER = {}

PROTOCOLS = list()

def parse_input():
    """Parses command line input."""
    parser = argparse.ArgumentParser(description="Autograder parser")
    parser.add_argument('-m', '--mode', type=str, default=None,
                        help="Mode the autograder should run in.")
    return parser.parse_args()

def ok_main(cmd_line_args):
    """Run all relevant aspects of ok.py."""
    name_to_protocol = dict()

    for protocol in PROTOCOLS:
        proto_inst = protocol(cmd_line_args)
        name_to_protocol[protocol.name] = proto_inst
        proto_inst.on_start(INPUT_BUFFER)

    send_to_server(INPUT_BUFFER)

    if cmd_line_args.mode != None:
        name_to_protocol[cmd_line_args.mode].on_interact(INPUT_BUFFER)

    send_to_server(INPUT_BUFFER)

if __name__ == '__main__':
    ok_main(parse_input())

@@ -123,16 +123,18 @@
         
+  
 Run unlo
@@ -147,29 +147,10 @@
 ests
-, check for updates,
  
+(
 and 
@@ -154,20 +154,26 @@
 nd save 
+your 
 work
+)
 .%0A  pyth
@@ -189,16 +189,18 @@
  -u     
+  
 Unlock n
@@ -246,16 +246,18 @@
  -h     
+  
 Display 
@@ -1523,16 +1523,17 @@
 od.%0A%22%22%22%0A
+%0A
 import a
@@ -2694,191 +2694,8 @@
 .%0A%0A%0A
-# TODO(denero) Pass around a buffer that checks for message duplication, rather%0A#              than using a global variable with shared access.%0AINPUT_BUFFER = %7B%7D%0A%0APROTOCOLS = list()%0A%0A
 def 
@@ -2783,16 +2783,25 @@
 tParser(
+%0A        
 descript
@@ -2808,27 +2808,77 @@
 ion=
-%22Autograder parser%22
+__doc__,%0A        formatter_class=argparse.RawDescriptionHelpFormatter
 )%0A  
@@ -2905,109 +2905,250 @@
 t('-
-m
+a
 ', '--
-mode', type=str, default=None,%0A                        help=%22Mode the autograder should run in
+assignment', metavar='A', type=str,%0A                        help=%22assignment name to check. Partial names are ok.%22)%0A    parser.add_argument('-u', '--unlock', action='store_true',%0A                        help=%22unlock tests interactively
 .%22)%0A
@@ -3179,16 +3179,17 @@
 args()%0A%0A
+%0A
 def ok_m
@@ -3192,25 +3192,16 @@
 ok_main(
-cmd_line_
 args):%0A 
@@ -3248,32 +3248,24 @@
 %22%22%22%0A    
-name_to_
 protocol
  = dict(
@@ -3256,16 +3256,47 @@
 protocol
+s = %5BFileContents%5D%0A    messages
  = dict(
@@ -3322,17 +3322,17 @@
  in 
-PROTOCOLS
+protocols
 :%0A  
@@ -3338,25 +3338,16 @@
        p
-roto_inst
  = proto
@@ -3350,25 +3350,16 @@
 rotocol(
-cmd_line_
 args)%0A  
@@ -3368,268 +3368,68 @@
     
-name_to_protocol%5Bprotocol.name%5D = proto_inst%0A        proto_inst.on_start(INPUT_BUFFER)%0A%0A    send_to_server(INPUT_BUFFER)%0A%0A    if cmd_line_args.mode != None:%0A        name_to_protocol%5Bcmd_line_args.mode%5D.on_interact(INPUT_BUFFER)%0A%0A    send_to_server(INPUT_BUFFER
+messages%5Bp.name%5D = p.on_start()%0A%0A    send_to_server(messages
 )%0A%0Ai

Add command-line options that are easier to understand.