Ejemplo n.º 1
0
 def check_weight(weight):
     if weight == -1 or (weight != 0 and weight != 100):
         ut.sprint(
             "WARNING: Invalid answer weight for multiple choice question: "
             + str(weight))
         return False
     return True
Ejemplo n.º 2
0
def parse_multiple_choice_question(q_name, index):
    """
    Parse multiple choice question answers.

    Parameters
    ----------
    q_name: str
        name of the question
    
    index: int
        index of the question in cell array

    Returns
    -------
    [obj]
        parsed answers
    
    obj 
        extra attributes to add to the question
    """
    aindex = index + 1
    length_check = len(cells) <= aindex
    cell_type_check = cells[aindex]["metadata"]["ctype"] != "answer"

    if length_check or cell_type_check:
        raise Exception(
            "WARNING: multiple choice question has no answer cell.")

    answers, extra = parse_multiple_answers_question(q_name, index)

    def check_weight(weight):
        if weight == -1 or (weight != 0 and weight != 100):
            ut.sprint(
                "WARNING: Invalid answer weight for multiple choice question: "
                + str(weight))
            return False
        return True

    found_true = False
    final_answers = []
    for answer in answers:
        weight = answer["answer_weight"]
        if not check_weight(weight):
            ut.sprint("WARNING: invalid weight in answer list for question " +
                      q_name)
            continue

        if weight > 0:
            if found_true:
                ut.sprint("WARNING: multiple correct answers in question " +
                          q_name +
                          ", consider changing to multiple answers questions")
                continue
            else:
                found_true = True

        final_answers += [answer]

    return final_answers, extra
Ejemplo n.º 3
0
def parse_question(index):
    """
    Parse Question.

    Parameters
    ----------
    index: int
        index of the question in cell array

    Returns
    -------
    obj
        parsed question
    """
    question = {}

    for key, val, in cells[index]["metadata"].items():
        if key == "ctype":
            continue
        elif key == "quesnum":
            question["question_name"] = str(val)
        else:
            question[key] = val

    if "question_type" not in question:
        ut.sprint(
            "WARNING: question does not have question-type, not including question"
        )
        return None

    if "points_possible" not in question:
        question["points_possible"] = 1

    q_type = question["question_type"]

    if q_type not in supp_q_types:
        ut.sprint("WARNING: unsupported question type of " + q_type)
        return None

    if "warning" in supp_q_types[q_type]:
        ut.sprint("WARNING: " + supp_q_types[q_type]["warning"])

    try:
        question["answers"], extra = supp_q_types[q_type]["parser"](
            question["question_name"], index)
    except Exception as e:
        ut.sprint(str(e))
        return None

    question_text, image_paths = parse_md_text(cells[index]["source"])
    question["question_text"] = question_text
    extra["image_paths"] = image_paths
    question = {**question, **extra}

    return question
Ejemplo n.º 4
0
def parse_md_text(text):
    """
    Parse markdown into HTML, also check for latex.
 
    Parameters
    ----------
    text: str
        text to parse

    Returns
    -------
    str
        parsed text with Canvas latex

    [str]
        list of paths to images linked from this text
    """
    text = md.convert(text)

    html = BeautifulSoup(text, 'html.parser')
    image_paths = []

    for image in html.find_all("img"):
        image_name = image.get("src")
        image_path = os.path.join(file_dir, image.get("src"))
        image_path = os.path.abspath(image_path)

        if os.path.islink(image_path): continue
        if not os.path.exists(image_path):
            ut.sprint(
                "WARNING: image was not a link and does not exist on this computer: "
                + image_path)

        image_paths += [{"name": image_name, "path": image_path}]

    begin = 0
    while text.find("$$", begin) != -1:
        start = text.find("$$", begin)
        if start - 1 >= 0 and text[start - 1] == "\\":
            begin += 2
            continue
        end = text.find("$$", start + 2)
        if end == -1:
            break

        latex_str = to_canvas(text[start + 2:end])
        text = text.replace(text[start:end + 2], latex_str)
        begin += len(latex_str)

    begin = 0
    while text.find("$", begin) != -1:
        start = text.find("$", begin)
        if start - 1 >= 0 and text[start - 1] == "\\":
            begin += 1
            continue
        end = text.find("$", start + 1)
        if end == -1:
            break

        latex_str = to_canvas(text[start + 1:end], True)
        text = text.replace(text[start:end + 1], latex_str)
        begin += len(latex_str)

    return text.replace("\\", ""), image_paths
Ejemplo n.º 5
0
def parse_numerical_question(q_name, index):
    """
    Parse numerical question answers.

    Parameters
    ----------
    q_name: str
        name of the question
    
    index: int
        index of the question in cell array

    Returns
    -------
    [obj]
        parsed answers
    
    obj 
        extra attributes to add to the question
    """
    aindex = index + 1
    length_check = len(cells) <= aindex
    cell_type_check = cells[aindex]["metadata"]["ctype"] != "answer"

    if length_check or cell_type_check:
        raise Exception("WARNING: numerical question has no answer cell.")

    answers = []
    for line in cells[index + 1]["source"].split("\n"):
        if line.startswith("*"):
            v1 = 0
            v2 = 0
            answer = {"answer_text": "", "answer_weight": 100}
            text = line[1:].strip(" ")
            if "," not in text:
                if ":" in text:
                    ut.sprint(
                        "WARNING: numerical answer has one value but has answer type, "
                        + "only one of these is allowed")
                v1 = float(text)
                answer["numerical_answer_type"] = "exact_answer"
            elif ":" not in text:
                ind = text.index(",")
                v1 = float(text[:ind].strip(" "))
                v2 = float(text[ind + 1:].strip(" "))
                answer["numerical_answer_type"] = "exact_answer"
            else:
                ind = text.index(":")
                vals = text[:ind]
                answer["numerical_answer_type"] = text[ind +
                                                       1:].strip(" ").replace(
                                                           "-", "_")
                ind = vals.index(",")
                v1 = float(vals[:ind].strip(" "))
                v2 = float(vals[ind + 1:].strip(" "))

            if answer["numerical_answer_type"] == "exact_answer":
                answer["answer_exact"] = v1
                answer["answer_error_margin"] = v2
            elif answer["numerical_answer_type"] == "range_answer":
                answer["answer_range_start"] = v1
                answer["answer_range_end"] = v2
            elif answer["numerical_answer_type"] == "precision_answer":
                answer["answer_approximate"] = v1
                answer["answer_precision"] = v2
            else:
                ut.sprint("WARNING: numerical answer type not supported: " +
                          answer["numerical_answer_type"])
                continue

            answers += [answer]

    return answers, {}
Ejemplo n.º 6
0
def md2canvas(url, notebook_file, token, token_file, course_id, save_settings,
              quiz_id, dump, no_upload, hush, reset):
    """
    Parse file into quiz and upload to Canvas.
    """
    # Set printing settings
    ut.hush = hush

    # Check argument validity
    if not notebook_file or not path.exists(notebook_file):
        print("Invalid notebook file.")
        return

    if not notebook_file.endswith(".md") and \
        not notebook_file.endswith(".ipynb"):
        print("Notebook file must be Markdown or Jupyter Notebook.")
        return

    if token and token_file:
        print("Only one of token or token file can be used.")
        return

    if token_file and not path.exists(token_file):
        print("Invalid token file.")
        return

    if no_upload and not dump:
        print(
            "WARNING: not dumping to file or uploading, nothing will happen.")

    # Get/Set configuration
    if token_file:
        with open(token_file, mode="r") as tf:
            token = tf.read()

    config_file = path.join(path.dirname(path.realpath(__file__)),
                            "config.yaml")
    if not path.exists(config_file):
        with open(config_file, "w") as f:
            f.close()
            reset = True

    with open(config_file, "r") as f:

        config = yaml.load(f, Loader=yaml.FullLoader)
        if not config:
            config = {}

        if save_settings or reset:
            if url:
                config["url"] = url
            elif reset:
                config["url"] = "https://canvas.ubc.ca"
            if token:
                config["token"] = token
            elif reset:
                config["token"] = "11224~PLAy00HrlbYVp7a6DV0a6X7pGQ13uLukhxF4ouz3JUeDJ" + \
                                  "R9dzY0hazkcDOlUuY0t"
            if course_id:
                config["course_id"] = course_id
            elif reset:
                config["course_id"] = "51824"

            with open(config_file, "w") as wf:
                yaml.dump(config, wf)

        if not no_upload:
            if not url:
                url = config["url"]
            if not token:
                token = config["token"]
            if not course_id:
                course_id = str(config["course_id"])

    ut.sprint("Parsing the quiz at " + notebook_file)
    quiz = m2j.parse_quiz(notebook_file)

    if dump:
        with open(dump, "w") as f:
            json.dump(f, quiz)

    if not no_upload:
        ut.sprint("Uploading quiz to Canvas with following settings:")
        ut.sprint("  URL = " + url)
        ut.sprint("  Token = " + token[:4] + (len(token) - 8) * "*" +
                  token[-4:])
        ut.sprint("  Course ID = " + course_id)
        if quiz_id:
            ut.sprint("  Quiz ID = " + quiz_id)
            j2c.update_quiz(cv, quiz, url, token, course_id, quiz_id)
        else:
            j2c.upload_quiz(cv, quiz, url, token, course_id)