Пример #1
0
    def post(self, query_string, data=None, handle_errors=True):
        """Generates a POST query on the target Trac system.

        This also alters the given data to include the form token stored on the
        cookies. Without this token, Trac will refuse form submissions.

        :param query_string: Starts with a slash, part of the URL between the
                             domain and the parameters (before the ?).
        :param data: Dictionary of parameters to encode and transmit to the
                     target page.
        :param handle_errors: Crash with a proper exception according to the
                              HTTP return code (default: True).

        """
        if data:
            data["__FORM_TOKEN"] = self.get_form_token()

        r = self.session.post(self.base_url + query_string, data=data)

        if r.status_code >= 400 and handle_errors:
            message = text.extract_message(r.text)
            if not message:
                message = "{} returned {}".format(self.base_url, r)
            raise exceptions.FatalError(message)

        return r
Пример #2
0
    def get(self, query_string, data=None, handle_errors=True):
        """Generates a GET query on the target Trac system.

        TODO: extract all the possible error elements as message.

        :param query_string: Starts with a slash, part of the URL between the
                             domain and the parameters (before the ?).
        :param data: Dictionary of parameters to encode at the end of the
                     ``query_string``.
        :param handle_errors: Crash with a proper exception according to the
                              HTTP return code (default: True).

        """
        r = self.session.get(self.base_url + query_string, data=data)

        if r.status_code >= 400 and handle_errors:
            message = text.extract_message(r.text)
            if not message:
                message = "{} returned {}".format(self.base_url, r)
            raise exceptions.FatalError(message)

        # Check the version if we can.
        self.check_version(r.text)

        return r
Пример #3
0
    def post(self, query_string, data=None, handle_errors=True):
        """Generates a POST query on the target Trac system.

        This also alters the given data to include the form token stored on the
        cookies. Without this token, Trac will refuse form submissions.

        :param query_string: Starts with a slash, part of the URL between the
                             domain and the parameters (before the ?).
        :param data: Dictionary of parameters to encode and transmit to the
                     target page.
        :param handle_errors: Crash with a proper exception according to the
                              HTTP return code (default: True).

        """
        if data:
            data["__FORM_TOKEN"] = self.get_form_token()

        r = self.session.post(self.base_url + query_string, data=data)

        if r.status_code >= 400 and handle_errors:
            message = text.extract_message(r.text)
            if not message:
                message = "{} returned {}".format(self.base_url, r)
            raise exceptions.FatalError(message)

        return r
Пример #4
0
    def get(self, query_string, data=None, handle_errors=True):
        """Generates a GET query on the target Trac system.

        TODO: extract all the possible error elements as message.

        :param query_string: Starts with a slash, part of the URL between the
                             domain and the parameters (before the ?).
        :param data: Dictionary of parameters to encode at the end of the
                     ``query_string``.
        :param handle_errors: Crash with a proper exception according to the
                              HTTP return code (default: True).

        """

        r = self.session.get(self.base_url + query_string, data=data)

        if r.status_code >= 400 and handle_errors:
            message = text.extract_message(r.text)
            if not message:
                message = "{} returned {}".format(self.base_url, r)
            raise exceptions.FatalError(message)

        # Check the version if we can.
        self.check_version(r.text)

        return r
Пример #5
0
    def run_new(self, owner=None):
        """Create a new ticket and return its id if successful.

        usage: cm new [owner]

        """
        template = self.resolve_template()

        if not template:
            template = DEFAULT_TEMPLATE

        # Parse the template ahead of time, allowing us to insert the Owner/To.
        ep = email.parser.Parser()
        em = ep.parsestr(template)
        body = em.get_payload()
        headers = OrderedDict(em.items())

        # The owner specified on the command line always prevails.
        if owner:
            headers["To"] = owner

        # If all else fail, assign it to yourself.
        if not headers["To"]:
            headers["To"] = self.username

        valid = False
        while not valid:
            # Get the properties at each iteration, in case an admin updated
            # the list in the mean time.
            properties = self.get_properties()

            # Assume the user will produce a valid ticket
            valid = True

            # Load the current values in a temp file for editing
            (fd, filename) = tempfile.mkstemp(suffix=".cm.ticket")
            fp = os.fdopen(fd, "w")

            fp.write(self._format_headers(headers))
            fp.write("\n\n")
            fp.write(body)

            fp.close()
            self.editor(filename)

            # Use the email parser to get the headers.
            ep = email.parser.Parser()
            with open(filename, "r") as fp:
                em = ep.parse(fp)

            os.unlink(filename)

            body = em.get_payload()
            headers = OrderedDict(em.items())

            errors = []
            fuzzy_match_fields = ("Milestone", "Component", "Type", "Version",
                                  "Priority")

            # Ensures all the required fields are filled-in
            for key in self.required_fields:
                if key in fuzzy_match_fields:
                    continue
                if not headers.get(key) or "**ERROR**" in headers[key]:
                    errors.append("Invalid '{}': cannot be blank".format(key))

            # Some fields are tolerant to incomplete values, this is where we
            # try to complete them.
            for key in fuzzy_match_fields:
                lkey = key.lower()
                if lkey not in properties:
                    continue

                valid_options = properties[lkey]["options"]

                # The specified value is not available in the multi-choice.
                if key in headers and headers[key] not in valid_options:
                    m = text.fuzzy_find(headers[key], valid_options)
                    if m:
                        # We found a close match, update the value with it.
                        headers[key] = m
                    else:
                        # We didn't find a close match. If the user entered
                        # something explicitly or if this field is required,
                        # this is an error, else just wipe the value and move
                        # on.
                        if headers[key] or key in self.required_fields:
                            joined_options = ", ".join(valid_options)
                            errors.append(u"Invalid '{}': expected: {}"
                                          .format(key, joined_options))
                        else:
                            headers[key] = ""

            if errors:
                valid = False
                print("\nFound the following errors:")
                for error in errors:
                    print(u" - {}".format(error))

                try:
                    self.input("\n-- Hit Enter to return to editor, "\
                              "^C to abort --\n")
                except KeyboardInterrupt:
                    raise exceptions.FatalError("ticket creation interrupted")

        # Since the body is expected to be using CRLF line termination, we
        # replace newlines by CRLF if no CRLF is found.
        if "\r\n" not in body:
            body = body.replace("\n", "\r\n")

        fields_data = {
            "field_summary": headers.get("Subject", ""),
            "field_type": headers.get("Type", ""),
            "field_version": headers.get("Version", ""),
            "field_description": body,
            "field_milestone": headers.get("Milestone", ""),
            "field_component": headers.get("Component", ""),
            "field_owner": headers.get("To", ""),
            "field_keywords": headers.get("Keywords", ""),
            "field_cc": headers.get("Cc", ""),
            "field_attachment": "",
        }

        # Assume anything outside of the original headers it to be included as
        # fields.
        for key, value in headers.items():
            field_name = "field_" + key.lower()
            if field_name not in fields_data:
                fields_data[field_name] = value

        r = self.post("/newticket", fields_data)

        if r.status_code != 200:
            message = text.extract_message(r.text)
            if not message:
                message = "unable to create new ticket"
            raise exceptions.RequestException(message)

        try:
            ticket_id = int(r.url.split("/")[-1])
        except:
            raise exceptions.RequestException("returned ticket_id is invalid.")

        self.open_in_browser_on_request(ticket_id)

        return ["ticket #{} created".format(ticket_id)]
Пример #6
0
    def run_new(self, owner=None):
        """Create a new ticket and return its id if successful.

        usage: cm new [owner]

        """
        template = self.resolve_template()

        if not template:
            if self.message_file:
                if self.message_file == "-":
                    template = sys.stdin.read()
                else:
                    with open(self.message_file) as fp:
                        template = fp.read()
            else:
                template = DEFAULT_TEMPLATE

        # Parse the template ahead of time, allowing us to insert the Owner/To.
        ep = email.parser.Parser()
        em = ep.parsestr(template)
        body = em.get_payload()
        headers = OrderedDict(em.items())

        # The owner specified on the command line always prevails.
        if owner:
            headers["To"] = owner

        # If all else fail, assign it to yourself.
        if not headers["To"]:
            headers["To"] = self.username

        self.login()

        valid = False
        while not valid:
            # Get the properties at each iteration, in case an admin updated
            # the list in the mean time.
            options = self.get_property_options()

            # Assume the user will produce a valid ticket
            valid = True

            # Load the current values in a temp file for editing
            (fd, filename) = tempfile.mkstemp(suffix=".cm.ticket")
            fp = os.fdopen(fd, "w")

            fp.write(self._format_headers(headers))
            fp.write("\n\n")
            fp.write(body)

            fp.close()

            # When reading the message from stdin, we can't edit, skip editor.
            if not self.message_file:
                self.editor(filename)

            # Use the email parser to get the headers.
            ep = email.parser.Parser()
            with open(filename, "r") as fp:
                em = ep.parse(fp)

            os.unlink(filename)

            body = em.get_payload()
            headers = OrderedDict(em.items())

            errors = []
            fuzzy_match_fields = ("Milestone", "Component", "Type", "Version",
                                  "Priority")

            # Ensures all the required fields are filled-in
            for key in self.required_fields:
                if key in fuzzy_match_fields:
                    continue
                if not headers.get(key) or "**ERROR**" in headers[key]:
                    errors.append("Invalid '{}': cannot be blank".format(key))

            # Some fields are tolerant to incomplete values, this is where we
            # try to complete them.
            for key in fuzzy_match_fields:
                lkey = key.lower()
                if lkey not in options:
                    continue

                valid_options = options[lkey]

                # The specified value is not available in the multi-choice.
                if key in headers and headers[key] not in valid_options:
                    m = text.fuzzy_find(headers[key], valid_options)
                    if m:
                        # We found a close match, update the value with it.
                        headers[key] = m
                    else:
                        # We didn't find a close match. If the user entered
                        # something explicitly or if this field is required,
                        # this is an error, else just wipe the value and move
                        # on.
                        if headers[key] or key in self.required_fields:
                            joined_options = ", ".join(valid_options)
                            errors.append(u"Invalid '{}': expected: {}".format(
                                key, joined_options))
                        else:
                            headers[key] = ""

            if errors:
                valid = False
                print("\nFound the following errors:")
                for error in errors:
                    print(u" - {}".format(error))

                try:
                    if not self.message_file:
                        self.input("\n-- Hit Enter to return to editor, "
                                   "^C to abort --\n")
                except KeyboardInterrupt:
                    raise exceptions.FatalError("ticket creation interrupted")

            # There is no editor loop when reading message from stdin, just
            # print the errors and exit.
            if self.message_file:
                break

        # Since the body is expected to be using CRLF line termination, we
        # replace newlines by CRLF if no CRLF is found.
        if "\r\n" not in body:
            body = body.replace("\n", "\r\n")

        fields_data = {
            "field_summary": headers.get("Subject", ""),
            "field_type": headers.get("Type", ""),
            "field_version": headers.get("Version", ""),
            "field_description": body,
            "field_milestone": headers.get("Milestone", ""),
            "field_component": headers.get("Component", ""),
            "field_owner": headers.get("To", ""),
            "field_keywords": headers.get("Keywords", ""),
            "field_cc": headers.get("Cc", ""),
            "field_attachment": "",
        }

        # Assume anything outside of the original headers it to be included as
        # fields.
        for key, value in headers.items():
            field_name = "field_" + key.lower()
            if field_name not in fields_data:
                fields_data[field_name] = value

        r = self.post("/newticket", fields_data)

        if r.status_code != 200:
            message = text.extract_message(r.text)
            if not message:
                message = "unable to create new ticket"
            raise exceptions.RequestException(message)

        try:
            ticket_id = int(r.url.split("/")[-1])
        except:
            raise exceptions.RequestException("returned ticket_id is invalid.")

        self.open_in_browser_on_request(ticket_id)

        return ["ticket #{} created".format(ticket_id)]