1import sys
2from io import BytesIO
3
4from pytest import importorskip, mark, raises
5
6from translate.misc.multistring import multistring
7from translate.storage import test_po
8
9
10pytestmark = mark.skipif(
11    not sys.platform.startswith("linux"), reason="cpo is only available on Linux"
12)
13
14
15cpo = importorskip("translate.storage.cpo")
16
17
18class TestCPOUnit(test_po.TestPOUnit):
19    UnitClass = cpo.pounit
20
21    def test_plurals(self):
22        """Tests that plurals are handled correctly."""
23        unit = self.UnitClass("Cow")
24        unit.msgid_plural = ["Cows"]
25        assert isinstance(unit.source, multistring)
26        assert unit.source.strings == ["Cow", "Cows"]
27        assert unit.source == "Cow"
28
29        unit.target = ["Koei", "Koeie"]
30        assert isinstance(unit.target, multistring)
31        assert unit.target.strings == ["Koei", "Koeie"]
32        assert unit.target == "Koei"
33
34        unit.target = {0: "Koei", 3: "Koeie"}
35        assert isinstance(unit.target, multistring)
36        assert unit.target.strings == ["Koei", "Koeie"]
37        assert unit.target == "Koei"
38
39        unit.target = ["Sk\u00ear", "Sk\u00eare"]
40        assert isinstance(unit.target, multistring)
41        assert unit.target.strings == ["Sk\u00ear", "Sk\u00eare"]
42        assert unit.target.strings == ["Sk\u00ear", "Sk\u00eare"]
43        assert unit.target == "Sk\u00ear"
44
45    def test_plural_reduction(self):
46        """checks that reducing the number of plurals supplied works"""
47        unit = self.UnitClass("Tree")
48        unit.msgid_plural = ["Trees"]
49        assert isinstance(unit.source, multistring)
50        assert unit.source.strings == ["Tree", "Trees"]
51        unit.target = multistring(["Boom", "Bome", "Baie Bome"])
52        assert isinstance(unit.source, multistring)
53        assert unit.target.strings == ["Boom", "Bome", "Baie Bome"]
54        unit.target = multistring(["Boom", "Bome"])
55        assert unit.target.strings == ["Boom", "Bome"]
56        unit.target = "Boom"
57        # FIXME: currently assigning the target to the same as the first string won't change anything
58        # we need to verify that this is the desired behaviour...
59        assert unit.target.strings[0] == "Boom"
60        unit.target = "Een Boom"
61        assert unit.target.strings == ["Een Boom"]
62
63    def test_notes(self):
64        """tests that the generic notes API works"""
65        unit = self.UnitClass("File")
66        assert unit.getnotes() == ""
67        unit.addnote("Which meaning of file?")
68        assert unit.getnotes("translator") == "Which meaning of file?"
69        assert unit.getnotes("developer") == ""
70        unit.addnote("Verb", origin="programmer")
71        assert unit.getnotes("developer") == "Verb"
72        unit.addnote("Thank you", origin="translator")
73        assert unit.getnotes("translator") == "Which meaning of file?\nThank you"
74        assert unit.getnotes() == "Which meaning of file?\nThank you\nVerb"
75        with raises(ValueError):
76            unit.getnotes("devteam")
77
78    def test_notes_withcomments(self):
79        """tests that when we add notes that look like comments that we treat them properly"""
80        unit = self.UnitClass("File")
81        unit.addnote("# Double commented comment")
82        assert unit.getnotes() == "# Double commented comment"
83        assert not unit.hastypecomment("c-format")
84
85
86class TestCPOFile(test_po.TestPOFile):
87    StoreClass = cpo.pofile
88
89    def test_msgidcomments(self):
90        """checks that we handle msgid comments"""
91        posource = 'msgid "test me"\nmsgstr ""'
92        pofile = self.poparse(posource)
93        thepo = pofile.units[0]
94        thepo.msgidcomment = "first comment"
95        print(pofile)
96        print("Blah", thepo.source)
97        assert thepo.source == "test me"
98        thepo.msgidcomment = "second comment"
99        assert bytes(pofile).count(b"_:") == 1
100
101    @mark.xfail(reason="Were disabled during port of Pypo to cPO - they might work")
102    def test_merge_duplicates_msgctxt(self):
103        """checks that merging duplicates works for msgctxt"""
104        posource = '#: source1\nmsgid "test me"\nmsgstr ""\n\n#: source2\nmsgid "test me"\nmsgstr ""\n'
105        pofile = self.poparse(posource)
106        assert len(pofile.units) == 2
107        pofile.removeduplicates("msgctxt")
108        print(pofile)
109        assert len(pofile.units) == 2
110        assert str(pofile.units[0]).count("source1") == 2
111        assert str(pofile.units[1]).count("source2") == 2
112
113    @mark.xfail(reason="Were disabled during port of Pypo to cPO - they might work")
114    def test_merge_blanks(self):
115        """checks that merging adds msgid_comments to blanks"""
116        posource = (
117            '#: source1\nmsgid ""\nmsgstr ""\n\n#: source2\nmsgid ""\nmsgstr ""\n'
118        )
119        pofile = self.poparse(posource)
120        assert len(pofile.units) == 2
121        pofile.removeduplicates("merge")
122        assert len(pofile.units) == 2
123        print(pofile.units[0].msgidcomments)
124        print(pofile.units[1].msgidcomments)
125        assert cpo.unquotefrompo(pofile.units[0].msgidcomments) == "_: source1\n"
126        assert cpo.unquotefrompo(pofile.units[1].msgidcomments) == "_: source2\n"
127
128    @mark.xfail(reason="Were disabled during port of Pypo to cPO - they might work")
129    def test_msgid_comment(self):
130        """checks that when adding msgid_comments we place them on a newline"""
131        posource = '#: source0\nmsgid "Same"\nmsgstr ""\n\n#: source1\nmsgid "Same"\nmsgstr ""\n'
132        pofile = self.poparse(posource)
133        assert len(pofile.units) == 2
134        pofile.removeduplicates("msgid_comment")
135        assert len(pofile.units) == 2
136        assert cpo.unquotefrompo(pofile.units[0].msgidcomments) == "_: source0\n"
137        assert cpo.unquotefrompo(pofile.units[1].msgidcomments) == "_: source1\n"
138        # Now lets check for formating
139        for i in (0, 1):
140            expected = (
141                """#: source%d\nmsgid ""\n"_: source%d\\n"\n"Same"\nmsgstr ""\n"""
142                % (i, i)
143            )
144            assert pofile.units[i].__str__() == expected
145
146    @mark.xfail(reason="Were disabled during port of Pypo to cPO - they might work")
147    def test_keep_blanks(self):
148        """checks that keeping keeps blanks and doesn't add msgid_comments"""
149        posource = (
150            '#: source1\nmsgid ""\nmsgstr ""\n\n#: source2\nmsgid ""\nmsgstr ""\n'
151        )
152        pofile = self.poparse(posource)
153        assert len(pofile.units) == 2
154        pofile.removeduplicates("keep")
155        assert len(pofile.units) == 2
156        # check we don't add msgidcomments
157        assert cpo.unquotefrompo(pofile.units[0].msgidcomments) == ""
158        assert cpo.unquotefrompo(pofile.units[1].msgidcomments) == ""
159
160    def test_output_str_unicode(self):
161        """checks that we can serialize pofile, unit content is in unicode"""
162        posource = """#: nb\nmsgid "Norwegian Bokmål"\nmsgstr ""\n"""
163        pofile = self.StoreClass(BytesIO(posource.encode("UTF-8")), encoding="UTF-8")
164        assert len(pofile.units) == 1
165        print(bytes(pofile))
166        thepo = pofile.units[0]
167        #        assert bytes(pofile) == posource.encode("UTF-8")
168        # extra test: what if we set the msgid to a unicode? this happens in prop2po etc
169        thepo.source = "Norwegian Bokm\xe5l"
170        #        assert str(thepo) == posource.encode("UTF-8")
171        # Now if we set the msgstr to Unicode
172        # this is an escaped half character (1/2)
173        halfstr = b"\xbd ...".decode("latin-1")
174        thepo.target = halfstr
175        #        assert halfstr in bytes(pofile).decode("UTF-8")
176        thepo.target = halfstr.encode("UTF-8")
177
178    #        assert halfstr.encode("UTF-8") in bytes(pofile)
179
180    def test_posections(self):
181        """checks the content of all the expected sections of a PO message"""
182        posource = '# other comment\n#. automatic comment\n#: source comment\n#, fuzzy\nmsgid "One"\nmsgstr "Een"\n'
183        pofile = self.poparse(posource)
184        print(pofile)
185        assert len(pofile.units) == 1
186        assert bytes(pofile).decode("utf-8") == posource
187
188    def test_multiline_obsolete(self):
189        """Tests for correct output of mulitline obsolete messages"""
190        posource = '#~ msgid ""\n#~ "Old thing\\n"\n#~ "Second old thing"\n#~ msgstr ""\n#~ "Ou ding\\n"\n#~ "Tweede ou ding"\n'
191        pofile = self.poparse(posource)
192        print("Source:\n%s" % posource)
193        print("Output:\n%s" % bytes(pofile))
194        assert len(pofile.units) == 1
195        assert pofile.units[0].isobsolete()
196        assert not pofile.units[0].istranslatable()
197        assert bytes(pofile).decode("utf-8") == posource
198
199    def test_unassociated_comments(self):
200        """tests behaviour of unassociated comments."""
201        oldsource = '# old lonesome comment\n\nmsgid "one"\nmsgstr "een"\n'
202        oldfile = self.poparse(oldsource)
203        print("serialize", bytes(oldfile))
204        assert len(oldfile.units) == 1
205        assert "# old lonesome comment\nmsgid" in bytes(oldfile).decode("utf-8")
206
207    @mark.xfail(reason="removal not working in cPO")
208    def test_remove(self):
209        super().test_remove()
210