Neustrukturierung der Python Skripte als Paket.
[wortliste.git] / skripte / python / edit_tools / prepare_patch.py
blob3c97a1ba552f1047a1b9b4aa1cd5a40caa8555fc
1 #!/usr/bin/env python
2 # -*- coding: utf8 -*-
3 # :Copyright: © 2014 Günter Milde.
4 # Released without warranty under the terms of the
5 # GNU General Public License (v. 2 or later)
6 # :Id: $Id: $
8 # prepare_patch.py: Helfer für kleine Editieraufgaben
9 # ===================================================
10 # ::
12 u"""
13 Erstelle einen Patch für kleinere Korrekturen der Wortliste.
14 Ausgangspunkt sind Dateien mit einer Korrektur pro Zeile.
15 (Zeilen, die mit ``#`` starten, werden ignoriert.)
17 AKTION ist eine von:
18 doppelte: Einträge mit gleichem Schlüssel entfernen,
19 fehleintraege: Einträge entfernen,
20 grossklein: Großschreibung ändern,
21 grossabgleich: Großschreibung der Trennmuster wie erstes Feld,
22 korrektur: Einträge durch alternative Version ersetzen.
23 neu: Einträge hinzufügen,
24 reformschreibung: Eintrag in "nur Reformschreibung" ändern.
25 zusammenfassen: Sprachvarianten zusammenfassen wenn gleich.
26 """
28 # Die ``<AKTION>.todo`` Dateien in diesem Verzeichnis beschreiben das
29 # jeweils erforderliche Datenformat im Dateikopf.
31 # ::
33 import optparse, sys, os, codecs
34 from copy import copy, deepcopy
37 from wortliste import WordFile, WordEntry, join_word, udiff, sortkey_duden
39 def teste_datei(datei):
40 """Teste, ob Datei geöffnet werden kann."""
42 try:
43 file = open(datei, 'r')
44 file.close()
45 except:
46 sys.stderr.write("Kann '" + datei + "' nicht öffnen\n" )
47 sys.exit()
50 # Sprachvarianten
51 # ---------------
52 # Sprach-Tag nach [BCP47]_::
54 sprachvariante = 'de-1901' # "traditionell"
55 # sprachvariante = 'de-1996' # Reformschreibung
56 # sprachvariante = 'de-x-GROSS' # ohne ß (Großbuchstaben und Kapitälchen)
57 # sprachvariante = 'de-1901-x-GROSS' # ohne ß (Schweiz oder GROSS)
58 # sprachvariante = 'de-1996-x-GROSS' # ohne ß (Schweiz oder GROSS)
59 # sprachvariante = 'de-CH-1901' # ohne ß (Schweiz) ("süssauer")
62 # Allgemeine Korrektur (z.B. Fehltrennung)
64 # Format:
65 # * ein Wort mit Trennstellen (für "Sprachvariante"):
66 # * vollständiger Eintrag (für Wörter mit Sprachvarianten).
68 # ::
70 def korrektur(wordfile, datei):
71 """Patch aus korrigierten Einträgen"""
73 if not datei:
74 datei = 'korrektur.todo'
75 teste_datei(datei)
77 korrekturen = {}
78 for line in open(datei, 'r'):
79 if line.startswith('#'):
80 continue
81 # Dekodieren, Zeilenende entfernen
82 line = line.decode('utf8').strip()
83 if not line:
84 continue
85 # Eintrag ggf. komplettieren
86 if u';' not in line:
87 line = u'%s;%s' % (join_word(line), line)
88 entry = WordEntry(line)
89 key = entry[0]
90 entry.regelaenderungen() # teste auf Dinge wie s-t/-st
92 korrekturen[key] = entry
94 wortliste = list(wordfile)
95 wortliste_neu = [] # korrigierte Liste
97 for entry in wortliste:
98 key = entry[0]
99 if key in korrekturen:
100 entry = korrekturen.pop(key)
101 wortliste_neu.append(entry)
103 if korrekturen:
104 print korrekturen # übrige Einträge
106 return (wortliste, wortliste_neu)
109 # Fehleinträge
110 # ------------
113 def fehleintraege(wordfile, datei):
114 """Entfernen der Einträge aus einer Liste von Fehleinträgen """
116 # Fehleinträge aus Datei.
118 # Format:
119 # Ein Eintrag/Zeile, mit oder ohne Trennzeichen
121 # ::
123 if not datei:
124 datei = 'fehleintraege.todo'
125 teste_datei(datei)
127 # Dekodieren, Zeilenende entfernen, Trennzeichen entfernen
128 korrekturen = set(join_word(
129 line.decode('utf8').strip().replace(u';', u' ').split()[0])
130 for line in open(datei, 'r')
131 if line.strip() and not line.startswith('#'))
132 wortliste = list(wordfile)
133 wortliste_neu = [] # korrigierte Liste
134 for entry in wortliste:
135 if entry[0] in korrekturen: # nicht kopieren
136 korrekturen.discard(entry[0]) # erledigt
137 else:
138 wortliste_neu.append(entry)
140 if korrekturen:
141 print 'nicht gefunden:'
142 for w in korrekturen:
143 print w.encode('utf8')
145 return (wortliste, wortliste_neu)
148 # Groß-/Kleinschreibung ändern
149 # ----------------------------
151 # Umstellen der Groß- oder Kleinschreibung auf die Variante in der Datei
152 # ``grossklein.todo``
154 # Format:
155 # ein Eintrag oder Wort pro Zeile, mit vorhandener Groß-/Kleinschreibung.
157 # ::
159 def grossklein(wordfile, datei):
160 """Groß-/Kleinschreibung umstellen"""
162 if not datei:
163 datei = 'grossklein.todo'
164 teste_datei(datei)
166 wortliste = list(wordfile)
168 # Dekodieren, Feldtrenner zu Leerzeichen
169 korrekturen = [line.decode('utf8').replace(';',' ')
170 for line in open(datei, 'r')
171 if not line.startswith('#')]
173 # erstes Feld, Trennzeichen entfernen
174 korrekturen = [join_word(line.split()[0]) for line in korrekturen
175 if line.strip() and not line.startswith('#')]
176 korrekturen = set(korrekturen)
177 wortliste_neu = deepcopy(wortliste) # korrigierte Liste
179 for entry in wortliste_neu:
180 if entry[0] in korrekturen:
181 korrekturen.discard(entry[0]) # gefunden
182 # Anfangsbuchstabe mit geänderter Großschreibung:
183 if entry[0][0].islower():
184 anfangsbuchstabe = entry[0][0].title()
185 else:
186 anfangsbuchstabe = entry[0][0].lower()
187 # Einträge korrigieren:
188 for i in range(len(entry)):
189 if entry[i].startswith('-'): # -2-, -3-, ...
190 continue
191 entry[i] = anfangsbuchstabe + entry[i][1:]
193 if korrekturen:
194 print korrekturen # übrige Einträge
196 return (wortliste, wortliste_neu)
198 # Anpassung der Großschreibung der Trennmuster an das erste Feld
199 # (ungetrenntes Wort). Siehe "wortliste.py" für einen Test auf Differenzen.
200 # (Differenzen sind größtenteils auf unvorsichtiges Ersetzen mit Texteditor
201 # zurückzuführen.)
202 # ::
204 def grossabgleich(wordfile):
205 wortliste = list(wordfile)
206 wortliste_neu = deepcopy(wortliste) # korrigierte Liste
207 for entry in wortliste_neu:
208 # Übertrag des Anfangsbuchstabens
209 for i in range(1,len(entry)):
210 if entry[i].startswith('-'):
211 continue
212 entry[i] = entry[0][0] + entry[i][1:]
213 return (wortliste, wortliste_neu)
216 # Sprachvariante ändern
217 # ---------------------
219 # Einträge mit allgemeingültiger (oder sonstwie mehrfacher) Sprachvariante
220 # in "nur in Reformschreibung" (allgemein) ändern.
222 # Format:
223 # ein Wort/(Alt-)Eintrag pro Zeile.
225 # ::
227 def reformschreibung(wordfile, datei):
228 """Wörter die nur in (allgemeiner) Reformschreibung existieren"""
230 if not datei:
231 datei='reformschreibung.todo'
232 teste_datei(datei)
233 # Dekodieren, Zeilenende entfernen
234 korrekturen = [line.decode('utf8').strip()
235 for line in open(datei, 'r')
236 if not line.startswith('#')
238 # erstes Feld
239 korrekturen = [line.split(';')[0] for line in korrekturen]
240 korrekturen = set(korrekturen)
242 wortliste = list(wordfile)
243 wortliste_neu = [] # korrigierte Liste
245 for entry in wortliste:
246 if entry[0] in korrekturen:
247 key = entry[0]
248 wort = entry.get('de-1996')
249 entry = WordEntry('%s;-2-;-3-;%s;%s' % (key, wort, wort))
250 korrekturen.discard(key) # erledigt
251 wortliste_neu.append(entry)
253 if korrekturen:
254 print korrekturen # übrige Einträge
255 return wortliste, wortliste_neu
258 # Getrennte Einträge für Sprachvarianten
259 # --------------------------------------
261 # Korrigiere fehlende Spezifizierung nach Sprachvarianten, z.B.
263 # - System;Sy-stem
264 # + System;-2-;Sy-stem;Sys-tem
266 # ::
268 def sprachvariante_split(wordfile, alt, neu,
269 altsprache='de-1901', neusprache='de-1996'):
271 wortliste = list(wordfile)
272 wortliste_neu = [] # korrigierte Liste
274 for entry in wortliste:
275 if len(entry) == 2: # Allgemeine Schreibung
276 altwort = entry.get(altsprache)
277 neuwort = altwort.replace(alt, neu)
278 if altwort != neuwort:
279 entry = WordEntry('%s;-2-;3;4' % (join_word(altwort)))
280 entry.set(altwort, altsprache)
281 entry.set(neuwort, neusprache)
282 wortliste_neu.append(entry)
283 return (wortliste, wortliste_neu)
287 # Neueinträge prüfen und vorbereiten
288 # ----------------------------------
290 # Die in einer Datei (ein Neueintrag pro Zeile) gesammelten Vorschläge auf
291 # auf Neuwert testen (vorhandene Wörter werden ignoriert, unabhängig von der
292 # Groß-/Kleinschreibung) und einsortieren.
294 # Akzeptierte Formate:
296 # * vollständiger Eintrag (für Wörter mit Sprachvarianten oder Kommentaren).
298 # * ein Wort mit Trennstellen:
300 # - Schlüssel wird generiert und vorangestellt (durch Semikolon getrennt).
301 # - Test auf einfache/häufige Reformänderungen 1996:
302 # s-t/-st, {ck/k-k}/c-k
304 # ::
306 def neu(wordfile, datei):
307 """Neueinträge prüfen und vorbereiten."""
309 if not datei:
310 datei = 'neu.todo'
311 teste_datei(datei)
312 korrekturen = open(datei, 'r')
314 wortliste = list(wordfile)
315 wortliste_neu = copy(wortliste)
316 words = set(entry[0] for entry in wortliste) # vorhandene Wörter
318 for line in korrekturen:
319 if line.startswith('#'):
320 continue
321 # Dekodieren, Zeilenende entfernen
322 line = line.decode('utf8').strip()
323 if not line:
324 continue
325 # Eintrag ggf. komplettieren:
326 if u';' not in line:
327 line = u'%s;%s' % (join_word(line), line)
328 entry = WordEntry(line)
329 key = entry[0]
330 # Test auf "Neuwert":
331 if key in words:
332 print key.encode('utf8'), 'schon vorhanden'
333 continue
334 if key.lower() in words or key.title() in words:
335 print key.encode('utf8'), 'mit anderer Großschreibung vorhanden'
336 continue
338 entry.regelaenderungen() # teste auf Dinge wie s-t/-st
339 wortliste_neu.append(entry)
340 words.add(key)
342 # Sortieren
343 wortliste_neu.sort(key=sortkey_duden)
345 return (wortliste, wortliste_neu)
348 def doppelte(wordfile, use_first=False):
349 """Doppeleinträge entfernen (ohne Berücksichtigung der Großschreibung).
351 Boolscher Wert `use_first` bestimmt, ob der erste oder der letzte von
352 Einträgen mit gleichem Schlüssel in der Liste verbleibt.
354 Die neue Liste ist sortiert.
356 wortliste = list(wordfile)
357 worddict = {}
358 for entry in wortliste:
359 key = entry[0].lower()
360 if use_first and key in worddict:
361 continue
362 worddict[key] = entry
364 wortliste_neu = worddict.values() # korrigierte Liste
365 wortliste_neu.sort(key=sortkey_duden)
367 print len(wortliste) - len(wortliste_neu), "Einträge entfernt"
368 return (wortliste, wortliste_neu)
371 def conflate(wortliste):
373 wortliste = list(wordfile)
374 wortliste_neu = [] # korrigierte Liste
376 for entry in wortliste:
377 if len(entry) > 2:
378 # Felder zusammenfassen:
379 entry = copy(entry)
380 entry.conflate_fields()
381 wortliste_neu.append(entry)
383 return (wortliste, wortliste_neu)
386 # Default-Aktion::
388 if __name__ == '__main__':
390 # Optionen::
392 usage = '%prog [Optionen] AKTION\n' + __doc__
394 parser = optparse.OptionParser(usage=usage)
395 parser.add_option('-i', '--file', dest='wortliste',
396 help='Eingangsdatei, Vorgabe "../../../wortliste"',
397 default='../../../wortliste')
398 parser.add_option('-k', '--todofile', dest='todo',
399 help='Korrekturdatei, Vorgabe "<AKTION>.todo"')
400 parser.add_option('-o', '--outfile', dest='patchfile',
401 help='Ausgangsdatei (Patch), Vorgabe "wortliste.patch"',
402 default='wortliste.patch')
404 (options, args) = parser.parse_args()
406 if args:
407 aktion = args[0]
408 else:
409 print 'Nichts zu tun: AKTION Argument fehlt.', '\n'
410 parser.print_help()
411 sys.exit()
413 # Die `Wortliste`::
414 wordfile = WordFile(options.wortliste)
416 # Da der Aufruf von `wortliste = list(wordfile)` lange dauert, wird er
417 # in den Aktionsroutinen nach dem Test auf die Eingabedatei ausgeführt.
419 # Behandeln::
421 if aktion == 'neu':
422 (wortliste, wortliste_neu) = neu(wordfile, options.todo)
423 elif aktion == 'doppelte':
424 (wortliste, wortliste_neu) = doppelte(wordfile)
425 elif aktion == 'fehleintraege':
426 (wortliste, wortliste_neu) = fehleintraege(wordfile, options.todo)
427 elif aktion == 'grossklein':
428 (wortliste, wortliste_neu) = grossklein(wordfile, options.todo)
429 elif aktion == 'grossabgleich':
430 (wortliste, wortliste_neu) = grossabgleich(wordfile)
431 elif aktion == 'korrektur':
432 (wortliste, wortliste_neu) = korrektur(wordfile, options.todo)
433 elif aktion == 'reformschreibung':
434 (wortliste, wortliste_neu) = reformschreibung(wordfile, options.todo)
435 elif aktion == 'zusammenfassen':
436 (wortliste, wortliste_neu) = conflate(wordfile)
437 else:
438 print 'Unbekannte AKTION', '\n'
439 parser.print_help()
440 sys.exit()
442 # (wortliste, wortliste_neu) = sprachvariante_split(wordfile,
443 # u'knien', u'kni-en')
445 # Patch erstellen::
447 patch = udiff(wortliste, wortliste_neu, 'wortliste', 'wortliste-neu',
448 encoding=wordfile.encoding)
449 if patch:
450 # print patch
451 patchfile = open(options.patchfile, 'w')
452 patchfile.write(patch + '\n')
453 print 'Änderungen nach %s geschrieben' % options.patchfile
454 else:
455 print 'keine Änderungen'