def test_parse_multiple_lines_mid(self):
        """Test parse to parse a line in the middle of multiple lines."""
        unit = GcodeParser()
        source = (
            "G28 ;Home\n" +
            "G1 X1 Y2\n" +
            "G20"
        )
        unit.parse(source)  # Read the first line

        result = unit.parse()

        self.assertEqual(result, unit, "The instance should be returned")
        self.assertParserProperties(
            unit=unit,
            source=source,
            offset=10,
            length=9,
            text="G1 X1 Y2",
            type="G",
            code=1,
            parameters="X1 Y2",
            parameterDict=OrderedDict([
                ("X", 1),
                ("Y", 2)
            ]),
            eol="\n"
        )
    def test_parameters_setter_valid_whitespace(self):
        """Test parameters setter when valid parameters are provided with extraneous whitespace."""
        unit = GcodeParser()
        unit.parse("G1")
        self.assertEqual(unit.commandString, "G1", "The commandString should be 'G1'.")

        unit.parameters = "   X  10 Y3.2 Foo  "

        self.assertEqual(
            unit.parameters, "X  10 Y3.2 Foo",
            "The parameters should be the expected value."
        )
        self.assertEqual(
            unit.commandString, "G1 X  10 Y3.2 Foo",
            "The commandString should be the expected value."
        )
        self.assertEqual(
            unit.parameterDict,
            OrderedDict([
                ("X", 10),
                ("Y", 3.2),
                ("F", None),
                ("O", None),
                ("", "Foo")
            ]),
            "The parameterDict should be the expected value."
        )
    def test_parameters_setter_valid_escapes(self):
        """Test the parameters setter when valid parameters with escaped chars are provided."""
        unit = GcodeParser()
        unit.parse("G1")
        self.assertEqual(unit.commandString, "G1", "The commandString should be 'G1'.")

        unit.parameters = r"X10 \\ \;Not a comment"

        self.assertEqual(
            unit.parameters, r"X10 \\ \;Not a comment",
            "The parameters should be the expected value."
        )
        self.assertEqual(
            unit.commandString, r"G1 X10 \\ \;Not a comment",
            "The commandString should be the expected value."
        )
        self.assertEqual(
            unit.parameterDict,
            OrderedDict([
                ("X", 10),
                ("N", None),
                ("O", None),
                ("T", None),
                ("A", None),
                ("C", None),
                ("M", None),
                ("E", None),
                ("", r"\\ \;Not a comment")
            ]),
            "The parameterDict should be the expected value."
        )
    def test_parameters_setter_invalid_comment(self):
        """Test parameters setter when parameters provided include a comment-like value."""
        unit = GcodeParser()
        unit.parse("G1")
        self.assertEqual(unit.commandString, "G1", "The commandString should be 'G1'.")

        with self.assertRaises(ValueError):
            unit.parameters = "X10 Y3.2 Foo ;Invalid"
    def test_stringify_leadingWhitespaceFalse(self):
        """Test stringify when the includeLeadingWhitespace parameter is False."""
        unit = GcodeParser()
        unit.parse("   G28")

        result = unit.stringify(includeLeadingWhitespace=False)

        self.assertEqual(result, "G28", "The result should have the leading whitespace removed")
    def test_stringify_leadingWhitespaceTrue(self):
        """Test stringify when the includeLeadingWhitespace parameter is True."""
        unit = GcodeParser()
        unit.parse("   G28")

        result = unit.stringify(includeLeadingWhitespace=True)

        self.assertEqual(result, "   G28", "The result should be the original text")
    def test_stringify_notGcodeText(self):
        """Test stringify when the text is not a valid Gcode line."""
        unit = GcodeParser()
        unit.parse("not gcode")

        result = unit.stringify()

        self.assertEqual(result, "not gcode", "The result should be the original text")
    def test_lineNumber_setter_none(self):
        """Test the lineNumber setter assigning None."""
        unit = GcodeParser()
        unit.parse("N1234 G0")
        self.assertEqual(unit.lineNumber, 1234, "The lineNumber should be 1234.")

        unit.lineNumber = None

        self.assertIsNone(unit.lineNumber, "The lineNumber should be None.")
        self.assertEqual(unit.gcode, "G0", "The gcode should be 'G0'.")
        self.assertEqual(unit.commandString, "G0", "The commandString should be 'G0'.")
    def test_stringify_lineNumberFalse_checksumFalse(self):
        """Test stringify when includeLineNumber=False and includeChecksum=False."""
        unit = GcodeParser()
        unit.parse("N123   G28")

        result = unit.stringify(includeLineNumber=False, includeChecksum=False)

        self.assertEqual(
            result, "G28",
            "The expected result should be returned"
        )
    def test_stringify_defaults(self):
        """Test stringify when no parameters are supplied."""
        unit = GcodeParser()

        unit.parse("   N0123   G028  X  *107   ; Comment   \r\n")

        result = unit.stringify()

        self.assertEqual(
            result, "   N123 G28 X *75 ; Comment   \r\n",
            "The result should be the normalized text"
        )
    def test_stringify_includeEolFalse(self):
        """Test stringify when includeEol=False."""
        unit = GcodeParser()

        unit.parse("G 28\n")
        self.assertEqual(unit.eol, "\n", "The eol should be '\\n'")

        result = unit.stringify(includeEol=False)

        self.assertEqual(
            result, "G28",
            "The expected result should be returned"
        )
    def test_stringify_includeCommentFalse_withComment(self):
        """Test stringify when includeComment=False and there is a comment."""
        unit = GcodeParser()

        unit.parse("G 28   ; My comment")
        self.assertIsNotNone(unit.comment, "The comment property should not be None")

        result = unit.stringify(includeComment=False)

        self.assertEqual(
            result, "G28",
            "The expected result should be returned"
        )
    def test_stringify_includeCommentTrue_commentNone(self):
        """Test stringify when includeComment=True and there is no comment."""
        unit = GcodeParser()

        unit.parse("G 28")
        self.assertIsNone(unit.comment, "The comment property should be None")

        result = unit.stringify(includeComment=True)

        self.assertEqual(
            result, "G28",
            "The expected result should be returned"
        )
    def test_stringify_subCodeNotNone(self):
        """Test stringify when the subCode is not None."""
        unit = GcodeParser()

        unit.parse("G 28.1")
        self.assertIsNotNone(unit.subCode, "The subCode property should not be None")

        result = unit.stringify()

        self.assertEqual(
            result, "G28.1",
            "The expected result should be returned"
        )
    def test_parameterDict_setter_emptyStr_none(self):
        """Test the parameterDict setter when passed {"": None}."""
        unit = GcodeParser()
        unit.parse("G28 X")
        self.assertEqual(
            unit.parameterDict, OrderedDict([("X", None), ("", "X")]),
            "The parameterDict should be the expected value"
        )

        unit.parameterDict = {"": None}

        self.assertIsNone(unit.parameters, "The parameters should be None")
        self.assertIsNone(unit.parameterDict, "The parameterDict should be None")
    def test_parameterItems_no_string_given(self):
        """Test that parameterItems uses the parameters property when no string is given."""
        unit = GcodeParser()
        unit.parse("G1 A1BC2")

        self.assertEqual(unit.parameters, "A1BC2", "The parameters should be 'A1BC2'.")

        result = [item for item in unit.parameterItems()]

        self.assertEqual(
            result, [("A", 1), ("B", None), ("C", 2), ("", "BC2")],
            "The expected result should be generated."
        )
    def test_stringify_parametersNotNone(self):
        """Test stringify when the parameters property is not None."""
        unit = GcodeParser()

        unit.parse("G 28 X  Y")
        self.assertIsNotNone(unit.parameters, "The parameters property should not be None")

        result = unit.stringify()

        self.assertEqual(
            result, "G28 X  Y",
            "The expected result should be returned"
        )
    def test_parameterDict_setter_label_none(self):
        """Test the parameterDict setter when passed a label with a value of None."""
        unit = GcodeParser()
        unit.parse("G28 X")
        self.assertEqual(
            unit.parameterDict, OrderedDict([("X", None), ("", "X")]),
            "The parameterDict should be the expected value"
        )

        unit.parameterDict = {"A": None}

        self.assertEqual(unit.parameters, "A", "The parameters should be 'A'")
        self.assertEqual(
            unit.parameterDict,
            OrderedDict([("A", None), ("", "A")]),
            "The parameterDict should be the expected value"
        )
    def test_parameterDict_setter_label_value(self):
        """Test the parameterDict setter when passed a label and non empty value."""
        unit = GcodeParser()
        unit.parse("G28 X")
        self.assertEqual(
            unit.parameterDict, OrderedDict([("X", None), ("", "X")]),
            "The parameterDict should be the expected value"
        )

        unit.parameterDict = {"A": 12}

        self.assertEqual(unit.parameters, "A12", "The parameters should be 'A12'")
        self.assertEqual(
            unit.parameterDict,
            OrderedDict([("A", 12)]),
            "The parameterDict should be the expected value"
        )
    def test_parameterDict_setter_noLabel_falsyValue(self):
        """Test the parameterDict setter when passed {"": "0"}."""
        unit = GcodeParser()
        unit.parse("G28 X")
        self.assertEqual(
            unit.parameterDict, OrderedDict([("X", None), ("", "X")]),
            "The parameterDict should be the expected value"
        )

        unit.parameterDict = {"": "0"}

        self.assertEqual(unit.parameters, "0", "The parameters should be '0'")
        self.assertEqual(
            unit.parameterDict,
            OrderedDict([("", "0")]),
            "The parameterDict should be the expected value"
        )
    def test_parse_multiple_lines_last(self):
        """Test parse to parse the last of multiple lines."""
        unit = GcodeParser()
        source = (
            "G28 ;Home\n" +
            "G1 X1 Y2\n" +
            "G20"
        )
        unit.parse(source)  # Read the first line
        unit.parse()        # Read the second line

        result = unit.parse()

        self.assertEqual(result, unit, "The instance should be returned")
        self.assertParserProperties(
            unit=unit,
            source=source,
            offset=19,
            length=3,
            text="G20",
            type="G",
            code=20
        )
    def test_parse_multiple_lines_first(self):
        """Test parse to parse the first of multiple lines."""
        unit = GcodeParser()
        source = (
            "G28 ;Home\n" +
            "G1 X1 Y2\n" +
            "G20"
        )

        result = unit.parse(source)

        self.assertEqual(result, unit, "The instance should be returned")
        self.assertParserProperties(
            unit=unit,
            source=source,
            offset=0,
            length=10,
            text="G28 ",
            type="G",
            code=28,
            comment=";Home",
            eol="\n"
        )
    def _test_parse_line(  # pylint: disable=too-many-arguments, too-many-locals
            self,
            source,
            offset=0,
            length=None,
            leadingWhitespace="",
            text="",
            trailingWhitespace="",
            lineNumber=None,
            type=None,  # pylint: disable=redefined-builtin
            code=None,
            subCode=None,
            parameters=None,
            parameterDict=None,
            rawChecksum=None,
            checksum=None,
            comment=None,
            eol=""
    ):
        """
        Test the parse method given a source string/offset and expected length and property values.

        Assertions will be made to verify the resulting values of the following properties, based
        on the parameter values provided:
            source, offset, length, leadingWhitespace, text, trailingWhitespace, lineNumber, type,
            code, gcode (based on the provided type and code), subCode, parameters, rawChecksum,
            checksum, comment, eol, and fullText (based on the provided string, offset and length).

        Parameters
        ----------
        source : string
            The source string to attempt to parse.
        offset : int
            The offset within the source to start parsing the line.
        length : int | None
            The expected length of the match.  If None, the entire source string will be expected
            to match.
        leadingWhitespace : string
            The expected parsed 'leadingWhitespace' value.
        text : string
            The expected parsed 'text' value.
        trailingWhitespace : string
            The expected parsed 'trailingWhitespace' value.
        lineNumber : int | None
            The expected parsed 'lineNumber' value.
        type : string | None
            The expected parsed 'type' value.
        code : int | None
            The expected parsed 'code' value.
        subCode : int | None
            The expected parsed 'subCode' value.
        parameters : string | None
            The expected parsed 'parameters' value.
        parameterDict : dict | None
            The expected parsed parameter labels and values.
        rawChecksum : string | None
            The expected parsed 'rawChecksum' value.
        checksum : int | None
            The expected parsed 'checksum' value.
        comment : string | None
            The expected parsed 'comment' value.
        eol : string
            The expected line ending value (CR, LF, CRLF, or empty string for none)

        Returns
        -------
        GcodeParser
            The GcodeParser instance created for the test, so additional assertions may be applied
            against its properties.
        """
        unit = GcodeParser()
        result = unit.parse(source, offset)
        self.assertParserProperties(
            unit=unit,
            source=source,
            offset=offset,
            length=length,
            leadingWhitespace=leadingWhitespace,
            text=text,
            trailingWhitespace=trailingWhitespace,
            lineNumber=lineNumber,
            type=type,
            code=code,
            subCode=subCode,
            parameters=parameters,
            parameterDict=parameterDict,
            rawChecksum=rawChecksum,
            checksum=checksum,
            comment=comment,
            eol=eol
        )
        self.assertEqual(result, unit, "The instance should be returned")