Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 89 additions & 7 deletions lint_po/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,53 @@ def process_pair(msgid, msgstr, file, line):
return True


def process_plural(msgid, msgid_plural, msgstrs, file, line):
# all placeholders that appear across msgid and msgid_plural
expected = set(extract(msgid)) | set(extract(msgid_plural))

ok = True
for idx, msgstr in msgstrs.items():
if not msgstr:
continue

actual = set(extract(msgstr))
missing = expected - actual
extra = actual - expected

if len(missing) or len(extra):
message = ""
if len(missing):
message += fail(f"Missing from msgstr[{idx}]: {', '.join(missing)}", file, line)
if len(extra):
message += fail(f"Unexpected in msgstr[{idx}]: {', '.join(extra)}", file, line)
message += f" at {file}:{line}"

print(f"Difference between msgid=\"{msgid}\" and msgstr[{idx}]=\"{msgstr}\":\n{message}\n", file=stderr)
ok = False

return ok


def process_file(filename, lines):
errors = False
state = 0
msgid = None
msgid_plural = None
msgstr = None
msgstrs = {}
msgstrs_idx = None
msgstrlineno = 0

def reset():
nonlocal state, msgid, msgid_plural, msgstr, msgstrs, msgstrs_idx, msgstrlineno
state = 0
msgid = None
msgid_plural = None
msgstr = None
msgstrs = {}
msgstrs_idx = None
msgstrlineno = 0

for lineno, line in enumerate(lines):
if re.match(r'^#', line):
continue
Expand All @@ -94,13 +134,18 @@ def process_file(filename, lines):
warnings.warn(f"({state}) Unexpected input: {line}")
errors = True

elif state == 1: # expecting `msgstr`, or more bits of previous msgid
elif state == 1: # expecting `msgstr`, `msgid_plural`, or more bits of previous msgid
if m := re.match(r'^msgstr\s+(.*)$', line):
msgstr = unqqbackslash(m[1])
msgstrlineno = lineno + 1
state = 2
continue

if m := re.match(r'^msgid_plural\s+(.*)$', line):
msgid_plural = unqqbackslash(m[1])
state = 3
continue

if re.match(r'^"', line):
msgid += unqqbackslash(line)
continue
Expand All @@ -113,10 +158,7 @@ def process_file(filename, lines):
if not process_pair(msgid, msgstr, filename, msgstrlineno):
errors = True

state = 0
msgid = None
msgstr = None
msgstrlineno = 0
reset()
continue

if re.match(r'^"', line):
Expand All @@ -126,8 +168,48 @@ def process_file(filename, lines):
warnings.warn(f"({state}) Unexpected input: {line}")
errors = True

if not process_pair(msgid, msgstr, filename, msgstrlineno):
errors = True
elif state == 3: # expecting `msgstr[0]`, or more bits of previous msgid_plural
if m := re.match(r'^msgstr\[(\d+)\]\s+(.*)$', line):
msgstrs_idx = int(m[1])
msgstrs[msgstrs_idx] = unqqbackslash(m[2])
msgstrlineno = lineno + 1
state = 4
continue

if re.match(r'^"', line):
msgid_plural += unqqbackslash(line)
continue

warnings.warn(f"({state}) Unexpected input: {line}")
errors = True

elif state == 4: # expecting newline, `msgstr[N+1]`, or more bits of previous msgstr[N]
if re.match(r'^$', line):
if not process_plural(msgid, msgid_plural, msgstrs, filename, msgstrlineno):
errors = True

reset()
continue

if m := re.match(r'^msgstr\[(\d+)\]\s+(.*)$', line):
msgstrs_idx = int(m[1])
msgstrs[msgstrs_idx] = unqqbackslash(m[2])
continue

if re.match(r'^"', line):
msgstrs[msgstrs_idx] += unqqbackslash(line)
continue

warnings.warn(f"({state}) Unexpected input: {line}")
errors = True

# handle EOF: flush whatever entry is pending
if state == 2:
if not process_pair(msgid, msgstr, filename, msgstrlineno):
errors = True
elif state == 4:
if not process_plural(msgid, msgid_plural, msgstrs, filename, msgstrlineno):
errors = True

return errors

Expand Down