def test_mark_mkmk_features(self, testufo):
        writer = MarkFeatureWriter()  # by default both mark + mkmk are built
        feaFile = ast.FeatureFile()
        assert writer.write(testufo, feaFile)

        assert str(feaFile) == dedent("""\
            markClass acutecomb <anchor 100 200> @MC_top;
            markClass tildecomb <anchor 100 200> @MC_top;

            feature mark {
                lookup mark2base {
                    pos base a <anchor 100 200> mark @MC_top;
                } mark2base;

                lookup mark2liga {
                    pos ligature f_i <anchor 100 500> mark @MC_top
                        ligComponent <anchor 600 500> mark @MC_top;
                } mark2liga;

            } mark;

            feature mkmk {
                lookup mark2mark_top {
                    @MFS_mark2mark_top = [acutecomb tildecomb];
                    lookupflag UseMarkFilteringSet @MFS_mark2mark_top;
                    pos mark tildecomb <anchor 100 300> mark @MC_top;
                } mark2mark_top;

            } mkmk;
            """)
    def test__makeMarkClassDefinitions_non_empty(self, FontClass):
        ufo = FontClass()
        ufo.newGlyph("a").appendAnchor({"name": "top", "x": 250, "y": 500})
        ufo.newGlyph("c").appendAnchor({"name": "bottom", "x": 250, "y": -100})
        ufo.newGlyph("grave").appendAnchor({
            "name": "_top",
            "x": 100,
            "y": 200
        })
        ufo.newGlyph("cedilla").appendAnchor({
            "name": "_bottom",
            "x": 100,
            "y": 0
        })
        ufo.features.text = dedent("""\
            markClass cedilla <anchor 200 0> @MC_bottom;
            markClass grave <anchor 100 200> @MC_top;
            """)

        writer = MarkFeatureWriter()
        feaFile = parseLayoutFeatures(ufo)
        writer.setContext(ufo, feaFile)
        markClassDefs = writer._makeMarkClassDefinitions()

        assert len(markClassDefs) == 1
        assert len(feaFile.markClasses) == 3
        assert "MC_bottom" in feaFile.markClasses
        assert "MC_top" in feaFile.markClasses
        assert [str(mcd) for mcd in markClassDefs
                ] == ["markClass cedilla <anchor 100 0> @MC_bottom_1;"]
    def test_warn_duplicate_anchor_names(self, FontClass, caplog):
        caplog.set_level(logging.ERROR)

        ufo = FontClass()
        ufo.newGlyph("a").anchors = [
            {
                "name": "top",
                "x": 100,
                "y": 200
            },
            {
                "name": "top",
                "x": 200,
                "y": 300
            },
        ]

        writer = MarkFeatureWriter()
        feaFile = ast.FeatureFile()

        logger = "ufo2ft.featureWriters.markFeatureWriter.MarkFeatureWriter"
        with caplog.at_level(logging.WARNING, logger=logger):
            writer.setContext(ufo, feaFile)

        assert len(caplog.records) == 1
        assert "duplicate anchor 'top' in glyph 'a'" in caplog.text
    def test_insert_comment_middle(self, testufo):
        writer = MarkFeatureWriter()
        testufo.features.text = dedent("""\
            markClass acutecomb <anchor 100 200> @MC_top;
            feature mark {
                lookup mark1 {
                    pos base a
                        <anchor 100 200> mark @MC_top;
                } mark1;
                #
                # Automatic Code
                #
                lookup mark2 {
                    pos base a
                        <anchor 150 250> mark @MC_top;
                } mark2;
            } mark;
            """)
        feaFile = parseLayoutFeatures(testufo)

        with pytest.raises(
                InvalidFeaturesData,
                match="Insert marker has rules before and after, feature mark "
                "cannot be inserted.",
        ):
            writer.write(testufo, feaFile)

        # test append mode ignores insert marker
        generated = self.writeFeatures(testufo, mode="append")
        assert str(generated) == dedent("""\
            markClass tildecomb <anchor 100 200> @MC_top;

            feature mark {
                lookup mark2base {
                    pos base a
                        <anchor 100 200> mark @MC_top;
                } mark2base;

                lookup mark2liga {
                    pos ligature f_i
                            <anchor 100 500> mark @MC_top
                        ligComponent
                            <anchor 600 500> mark @MC_top;
                } mark2liga;

            } mark;

            feature mkmk {
                lookup mark2mark_top {
                    @MFS_mark2mark_top = [acutecomb tildecomb];
                    lookupflag UseMarkFilteringSet @MFS_mark2mark_top;
                    pos mark tildecomb
                        <anchor 100 300> mark @MC_top;
                } mark2mark_top;

            } mkmk;
            """)
    def test_defs_and_lookups_first(self, testufo):
        testufo.newGlyph("circumflexcomb")
        writer = MarkFeatureWriter()
        testufo.features.text = dedent("""\
            feature mkmk {
                # Automatic Code
                # Move acutecomb down and right if preceded by circumflexcomb
                lookup move_acutecomb {
                    lookupflag UseMarkFilteringSet [acutecomb circumflexcomb];
                    pos circumflexcomb acutecomb' <0 20 0 20>;
                } move_acutecomb;
            } mkmk;
            """)
        feaFile = parseLayoutFeatures(testufo)

        assert writer.write(testufo, feaFile)

        assert str(feaFile) == dedent("""\
            markClass acutecomb <anchor 100 200> @MC_top;
            markClass tildecomb <anchor 100 200> @MC_top;

            feature mark {
                lookup mark2base {
                    pos base a
                        <anchor 100 200> mark @MC_top;
                } mark2base;

                lookup mark2liga {
                    pos ligature f_i
                            <anchor 100 500> mark @MC_top
                        ligComponent
                            <anchor 600 500> mark @MC_top;
                } mark2liga;

            } mark;

            feature mkmk {
                lookup mark2mark_top {
                    @MFS_mark2mark_top = [acutecomb tildecomb];
                    lookupflag UseMarkFilteringSet @MFS_mark2mark_top;
                    pos mark tildecomb
                        <anchor 100 300> mark @MC_top;
                } mark2mark_top;

            } mkmk;

            feature mkmk {
                # Move acutecomb down and right if preceded by circumflexcomb
                lookup move_acutecomb {
                    lookupflag UseMarkFilteringSet [acutecomb circumflexcomb];
                    pos circumflexcomb acutecomb' <0 20 0 20>;
                } move_acutecomb;

            } mkmk;
            """)
    def test_skip_unnamed_anchors(self, FontClass, caplog):
        caplog.set_level(logging.ERROR)

        ufo = FontClass()
        ufo.newGlyph("a").appendAnchor({"x": 100, "y": 200})

        writer = MarkFeatureWriter()
        feaFile = ast.FeatureFile()

        logger = "ufo2ft.featureWriters.markFeatureWriter.MarkFeatureWriter"
        with caplog.at_level(logging.WARNING, logger=logger):
            writer.setContext(ufo, feaFile)

        assert len(caplog.records) == 1
        assert "unnamed anchor discarded in glyph 'a'" in caplog.text
    def test_insert_comment_outside_block(self, testufo):
        writer = MarkFeatureWriter()
        testufo.features.text = dedent("""\
            #
            # Automatic Code
            #
            """)
        feaFile = parseLayoutFeatures(testufo)

        assert writer.write(testufo, feaFile)

        testufo.features.text = dedent("""\
            #
            # Automatic Code
            #
            markClass acutecomb <anchor 100 200> @MC_top;
            feature mark {
                lookup mark1 {
                    pos base a <anchor 100 200> mark @MC_top;
                } mark1;

            } mark;
            """)
        feaFile = parseLayoutFeatures(testufo)

        assert writer.write(testufo, feaFile)

        # test append mode
        writer = MarkFeatureWriter(mode="append")
        assert writer.write(testufo, feaFile)
    def test__makeMarkClassDefinitions_empty(self, FontClass):
        ufo = FontClass()
        ufo.newGlyph("a").appendAnchor({"name": "top", "x": 250, "y": 500})
        ufo.newGlyph("c").appendAnchor({"name": "bottom", "x": 250, "y": -100})
        ufo.newGlyph("grave").appendAnchor({
            "name": "_top",
            "x": 100,
            "y": 200
        })
        ufo.newGlyph("cedilla").appendAnchor({
            "name": "_bottom",
            "x": 100,
            "y": 0
        })
        writer = MarkFeatureWriter()
        feaFile = ast.FeatureFile()
        writer.setContext(ufo, feaFile)
        markClassDefs = writer._makeMarkClassDefinitions()

        assert len(feaFile.markClasses) == 2
        assert [str(mcd) for mcd in markClassDefs] == [
            "markClass cedilla <anchor 100 0> @MC_bottom;",
            "markClass grave <anchor 100 200> @MC_top;",
        ]
    def test_write_only_one(self, testufo):
        writer = MarkFeatureWriter(features=["mkmk"])  # only builds "mkmk"
        feaFile = ast.FeatureFile()
        assert writer.write(testufo, feaFile)
        fea = str(feaFile)

        assert "feature mark" not in fea
        assert "feature mkmk" in fea

        writer = MarkFeatureWriter(features=["mark"])  # only builds "mark"
        feaFile = ast.FeatureFile()
        assert writer.write(testufo, feaFile)
        fea = str(feaFile)

        assert "feature mark" in fea
        assert "feature mkmk" not in fea
in_path = sys.argv[1]
out_path = sys.argv[2]
if len(sys.argv) == 4:
    stylename = sys.argv[3]
else:
    # e.g. "Bold" in "sources/masters/Rasa-Bold.ufo"
    stylename = re.search(r"Rasa\-([A-z]+)", out_path).group(1)

features = ""

# Generate markClass statements and prepend them
font = Font(out_path)
markClassDefinitions = []
feaFile = FeatureFile()  # A holder for the generated mark features

markWriter = MarkFeatureWriter()
markWriter.write(font, feaFile)
markFeatures = feaFile.asFea()  # Get the generated mark features

reMarkClass = re.compile(r"markClass.*;")
markClassDefinitions = reMarkClass.findall(
    markFeatures)  # Save all the markClass definitions

# Read the input feature file
with open(in_path, "r") as input_fea:
    # Replace $markClasses with the generated markClassDefinitions
    if markClassDefinitions != []:
        features = input_fea.read().replace(
            "$markClasses", "\n".join(markClassDefinitions))
    else:
        features = input_fea.read().replace("$markClasses", "")
    def test_insert_comment_after(self, testufo):
        writer = MarkFeatureWriter()
        testufo.features.text = dedent("""\
            markClass acutecomb <anchor 100 200> @MC_top;
            feature mark {
                lookup mark1 {
                    pos base a <anchor 100 200> mark @MC_top;
                } mark1;
                #
                # Automatic Code
                #
            } mark;
            """)
        feaFile = parseLayoutFeatures(testufo)

        assert writer.write(testufo, feaFile)

        assert str(feaFile) == dedent("""\
            markClass acutecomb <anchor 100 200> @MC_top;
            feature mark {
                lookup mark1 {
                    pos base a <anchor 100 200> mark @MC_top;
                } mark1;

                #
                #
            } mark;

            markClass tildecomb <anchor 100 200> @MC_top;

            feature mark {
                lookup mark2base {
                    pos base a <anchor 100 200> mark @MC_top;
                } mark2base;

                lookup mark2liga {
                    pos ligature f_i <anchor 100 500> mark @MC_top
                        ligComponent <anchor 600 500> mark @MC_top;
                } mark2liga;

            } mark;

            feature mkmk {
                lookup mark2mark_top {
                    @MFS_mark2mark_top = [acutecomb tildecomb];
                    lookupflag UseMarkFilteringSet @MFS_mark2mark_top;
                    pos mark tildecomb <anchor 100 300> mark @MC_top;
                } mark2mark_top;

            } mkmk;
            """)

        # test append mode ignores insert marker
        generated = self.writeFeatures(testufo, mode="append")
        assert str(generated) == dedent("""\
            markClass tildecomb <anchor 100 200> @MC_top;

            feature mark {
                lookup mark2base {
                    pos base a <anchor 100 200> mark @MC_top;
                } mark2base;

                lookup mark2liga {
                    pos ligature f_i <anchor 100 500> mark @MC_top
                        ligComponent <anchor 600 500> mark @MC_top;
                } mark2liga;

            } mark;

            feature mkmk {
                lookup mark2mark_top {
                    @MFS_mark2mark_top = [acutecomb tildecomb];
                    lookupflag UseMarkFilteringSet @MFS_mark2mark_top;
                    pos mark tildecomb <anchor 100 300> mark @MC_top;
                } mark2mark_top;

            } mkmk;
            """)