<commit_before>import datetime
import dateutil
import dateutil.parser
import json
import logging
import traceback
import re
import StringIO

from datetime import timedelta
from lxml import etree

from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
from progress import Progress
from capa.capa_problem import LoncapaProblem
from capa.responsetypes import StudentInputError
from static_replace import replace_urls

log = logging.getLogger("mitx.courseware")

#-----------------------------------------------------------------------------
TIMEDELTA_REGEX = re.compile(r'^((?P<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?) hour(?:s?))?(\s)?((?P<minutes>\d+?) minute(?:s)?)?(\s)?((?P<seconds>\d+?) second(?:s)?)?$')


def only_one(lst, default="", process=lambda x: x):
    """
    If lst is empty, returns default
    If lst has a single element, applies process to that element and returns it
    Otherwise, raises an exeception
    """
    if len(lst) == 0:
        return default
    elif len(lst) == 1:
        return process(lst[0])
    else:
        raise Exception('Malformed XML')


def parse_timedelta(time_str):
    """
    time_str: A string with the following components:
        <D> day[s] (optional)
        <H> hour[s] (optional)
        <M> minute[s] (optional)
        <S> second[s] (optional)

    Returns a datetime.timedelta parsed from the string
    """
    parts = TIMEDELTA_REGEX.match(time_str)
    if not parts:
        return
    parts = parts.groupdict()
    time_params = {}
    for (name, param) in parts.iteritems():
        if param:
            time_params[name] = int(param)
    return timedelta(**time_params)


class ComplexEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, complex):
            return "{real:.7g}{imag:+.7g}*j".format(real=obj.real, imag=obj.imag)
        return json.JSONEncoder.default(self, obj)


class CapaModule(XModule):
    ''' Interface between capa_problem and x_module. Originally a hack
    meant to be refactored out, but it seems to be serving a useful
    prupose now. We can e.g .destroy and create the capa_problem on a
    reset.
    '''
    icon_class = 'problem'

    def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs):
        XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs)

        self.attempts = 0
        self.max_attempts = None

        dom2 = etree.fromstring(definition['data'])

        self.explanation = "problems/" + only_one(dom2.xpath('/problem/@explain'),
                                                  default="closed")
        # TODO: Should be converted to: self.explanation=only_one(dom2.xpath('/problem/@explain'), default="closed")
        self.explain_available = only_one(dom2.xpath('/problem/@explain_available'))

        display_due_date_string = self.metadata.get('due', None)
        if display_due_date_string is not None:
            self.display_due_date = dateutil.parser.parse(display_due_date_string)
            #log.debug("Parsed " + display_due_date_string + " to " + str(self.display_due_date))
        else:
            self.display_due_date = None

        grace_period_string = self.metadata.get('graceperiod', None)
        if grace_period_string is not None and self.display_due_date:
            self.grace_period = parse_timedelta(grace_period_string)
            self.close_date = self.display_due_date + self.grace_period
            #log.debug("Then parsed " + grace_period_string + " to closing date" + str(self.close_date))
        else:
            self.grace_period = None
            self.close_date = self.display_due_date

        self.max_attempts = only_one(dom2.xpath('/problem/@attempts'))
        if len(self.max_attempts) > 0:
            self.max_attempts = int(self.max_attempts)
        else:
            self.max_attempts = None

        self.show_answer = self.metadata.get('showanwser', 'closed')

        if self.show_answer == "":
            self.show_answer = "closed"

        if instance_state != None:
            instance_state = json.loads(instance_state)
        if instance_state != None and 'attempts' in instance_state:
            self.attempts = instance_state['attempts']

        self.name = only_one(dom2.xpath('/problem/@name'))

        weight_string = only_one(dom2.xpath('/problem/@weight'))
        if weight_string:
            self.weight = float(weight_string)
        else:
            self.weight = 1

        if self.rerandomize == 'never':
            seed = 1
        elif self.rerandomize == "per_student" and hasattr(system, 'id'):
            seed = system.id
        else:
            seed = None

        try:
            self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(), instance_state, seed=seed, system=self.system)
        except Exception:
            msg = 'cannot create LoncapaProblem %s' % self.url
            log.exception(msg)
            if self.system.DEBUG:
                msg = '<p>%s</p>' % msg.replace('<', '&lt;')
                msg += '<p><pre>%s</pre></p>' % traceback.format_exc().replace('<', '&lt;')
                # create a dummy problem with error message instead of failing
                problem_text = '<problem><text><font color="red" size="+2">Problem %s has an error:</font>%s</text></problem>' % (self.location.url(), msg)
                self.lcp = LoncapaProblem(problem_text, self.location.html_id(), instance_state, seed=seed, system=self.system)
            else:
                raise

    @property
    def rerandomize(self):
        """<commit_msg>Fix url in CapaModule error message<commit_after>import datetime
import dateutil
import dateutil.parser
import json
import logging
import traceback
import re
import StringIO

from datetime import timedelta
from lxml import etree

from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
from progress import Progress
from capa.capa_problem import LoncapaProblem
from capa.responsetypes import StudentInputError
from static_replace import replace_urls

log = logging.getLogger("mitx.courseware")

#-----------------------------------------------------------------------------
TIMEDELTA_REGEX = re.compile(r'^((?P<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?) hour(?:s?))?(\s)?((?P<minutes>\d+?) minute(?:s)?)?(\s)?((?P<seconds>\d+?) second(?:s)?)?$')


def only_one(lst, default="", process=lambda x: x):
    """
    If lst is empty, returns default
    If lst has a single element, applies process to that element and returns it
    Otherwise, raises an exeception
    """
    if len(lst) == 0:
        return default
    elif len(lst) == 1:
        return process(lst[0])
    else:
        raise Exception('Malformed XML')


def parse_timedelta(time_str):
    """
    time_str: A string with the following components:
        <D> day[s] (optional)
        <H> hour[s] (optional)
        <M> minute[s] (optional)
        <S> second[s] (optional)

    Returns a datetime.timedelta parsed from the string
    """
    parts = TIMEDELTA_REGEX.match(time_str)
    if not parts:
        return
    parts = parts.groupdict()
    time_params = {}
    for (name, param) in parts.iteritems():
        if param:
            time_params[name] = int(param)
    return timedelta(**time_params)


class ComplexEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, complex):
            return "{real:.7g}{imag:+.7g}*j".format(real=obj.real, imag=obj.imag)
        return json.JSONEncoder.default(self, obj)


class CapaModule(XModule):
    ''' Interface between capa_problem and x_module. Originally a hack
    meant to be refactored out, but it seems to be serving a useful
    prupose now. We can e.g .destroy and create the capa_problem on a
    reset.
    '''
    icon_class = 'problem'

    def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs):
        XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs)

        self.attempts = 0
        self.max_attempts = None

        dom2 = etree.fromstring(definition['data'])

        self.explanation = "problems/" + only_one(dom2.xpath('/problem/@explain'),
                                                  default="closed")
        # TODO: Should be converted to: self.explanation=only_one(dom2.xpath('/problem/@explain'), default="closed")
        self.explain_available = only_one(dom2.xpath('/problem/@explain_available'))

        display_due_date_string = self.metadata.get('due', None)
        if display_due_date_string is not None:
            self.display_due_date = dateutil.parser.parse(display_due_date_string)
            #log.debug("Parsed " + display_due_date_string + " to " + str(self.display_due_date))
        else:
            self.display_due_date = None

        grace_period_string = self.metadata.get('graceperiod', None)
        if grace_period_string is not None and self.display_due_date:
            self.grace_period = parse_timedelta(grace_period_string)
            self.close_date = self.display_due_date + self.grace_period
            #log.debug("Then parsed " + grace_period_string + " to closing date" + str(self.close_date))
        else:
            self.grace_period = None
            self.close_date = self.display_due_date

        self.max_attempts = only_one(dom2.xpath('/problem/@attempts'))
        if len(self.max_attempts) > 0:
            self.max_attempts = int(self.max_attempts)
        else:
            self.max_attempts = None

        self.show_answer = self.metadata.get('showanwser', 'closed')

        if self.show_answer == "":
            self.show_answer = "closed"

        if instance_state != None:
            instance_state = json.loads(instance_state)
        if instance_state != None and 'attempts' in instance_state:
            self.attempts = instance_state['attempts']

        self.name = only_one(dom2.xpath('/problem/@name'))

        weight_string = only_one(dom2.xpath('/problem/@weight'))
        if weight_string:
            self.weight = float(weight_string)
        else:
            self.weight = 1

        if self.rerandomize == 'never':
            seed = 1
        elif self.rerandomize == "per_student" and hasattr(system, 'id'):
            seed = system.id
        else:
            seed = None

        try:
            self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(), instance_state, seed=seed, system=self.system)
        except Exception:
            msg = 'cannot create LoncapaProblem %s' % self.location.url()
            log.exception(msg)
            if self.system.DEBUG:
                msg = '<p>%s</p>' % msg.replace('<', '&lt;')
                msg += '<p><pre>%s</pre></p>' % traceback.format_exc().replace('<', '&lt;')
                # create a dummy problem with error message instead of failing
                problem_text = '<problem><text><font color="red" size="+2">Problem %s has an error:</font>%s</text></problem>' % (self.location.url(), msg)
                self.lcp = LoncapaProblem(problem_text, self.location.html_id(), instance_state, seed=seed, system=self.system)
            else:
                raise

    @property
    def rerandomize(self):
        """
{'fraction_non_alphanumeric': 0.13348037053374504, 'fraction_numerical': 0.002999558888398765, 'mean_word_length': 2.5794126934006947, 'pattern_counts': {'":': 2, '<': 47, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2966364', 'n_tokens_mistral': 3473, 'n_tokens_neox': 3312, 'n_words': 793}