#!/usr/bin/env python3 # -*- coding: UTF-8 -*- from Alphabet import RUNES from LPath import FILES_ALL, FILES_SOLVED, LPath from InterruptDB import InterruptDB from InterruptIndices import InterruptIndices from RuneText import RuneTextFile from Probability import Probability NORM_MIN = 0.40 NORM_MAX = 0.98 HIGH_MIN = 1.25 HIGH_MAX = 1.65 def mark(x, low=0, high=1): if x <= low: return ' class="m0"' return f' class="m{int((min(high, x) - low) / (high - low) * 14) + 1}"' ######################################### # InterruptToWeb : Read interrupt DB and create html graphic / matrix ######################################### class InterruptToWeb(object): def __init__(self, dbname, template='templates/ioc.html'): with open(LPath.results(template), 'r') as f: self.template = f.read() self.scores = InterruptDB.load_scores(dbname) def table_reliable(self): db_indices = InterruptIndices() trh = '' trtotal = 'Total' trd = [f'{x}' for x in RUNES] del_row = [True] * 29 for name in FILES_ALL: if name not in self.scores: continue total = db_indices.total(name) trh += f'
{name}
' trtotal += f'{total}' for i in range(29): scrs = self.scores[name][i][1:] if not scrs: trd[i] += '–' continue del_row[i] = False worst_irpc = min([x[1] for x in scrs]) if worst_irpc == 0: if max([x[1] for x in scrs]) != 0: trd[i] += '?' continue _, num = db_indices.consider(name, i, worst_irpc) trd[i] += f'{num}' trh += '\n' trtotal += '\n' for i in range(29): trd[i] += '\n' if del_row[i]: trd[i] = '' return f'{trh}{"".join(trd)}{trtotal}
' def table_interrupt(self, irp, pmin, pmax): maxkl = max(len(x[irp]) for x in self.scores.values()) trh = '' trbest = 'best' trd = [f'{x}' for x in range(maxkl)] for name in FILES_ALL: maxscore = 0 bestkl = -1 try: klarr = self.scores[name][irp] except KeyError: continue trh += f'
{name}
' for kl, (score, _) in enumerate(klarr): if score < 0: trd[kl] += f'–' else: trd[kl] += f'{score:.2f}' if score > maxscore: maxscore = score bestkl = kl trbest += f'{bestkl}' trh += '\n' trbest += '\n' for i in range(29): trd[i] += '\n' return f'{trh}{"".join(trd[1:])}{trbest}
' def make(self, outfile, pmin, pmax): nav = '' txt = '' for i in range(29): has_entries = any(True for x in self.scores.values() if x[i]) if not has_entries: continue nav += f'{RUNES[i]}\n' txt += f'

Interrupt {i}: {RUNES[i]}

' txt += self.table_interrupt(i, pmin, pmax) html = self.template.replace('__NAVIGATION__', nav) html = html.replace('__TAB_RELIABLE__', self.table_reliable()) html = html.replace('__INTERRUPT_TABLES__', txt) with open(LPath.results(outfile), 'w') as f: f.write(html) class ChapterToWeb(object): def __init__(self, template='templates/pages.html'): with open(LPath.results(template), 'r') as f: self.template = f.read() self.score = [(InterruptDB.load_scores('db_high'), HIGH_MIN, HIGH_MAX), (InterruptDB.load_scores('db_norm'), NORM_MIN, NORM_MAX)] def pick_ngrams(self, runes, gramsize, limit=100): res = {} for i in range(len(runes) - gramsize + 1): z = ''.join(x.rune for x in runes[i:i + gramsize]) try: res[z] += 1 except KeyError: res[z] = 1 res = sorted(res.items(), key=lambda x: -x[1]) txt = f'
{gramsize}-grams:
\n' txt += '
\n' for x, y in res[:limit]: txt += f'
{x}:
{y}
' if len(res) > limit: txt += f' + {len(res) - limit} others' return txt + '
\n' def sec_counts(self, words, runes): txt = '' txt += f'

Words: {len(words)}

\n' txt += f'

Runes: {len(runes)}

\n' txt += '
\n' rcount = [0] * 29 for r in runes: rcount[r.index] += 1 minmax = [min(rcount), max(rcount)] txt += '
1-grams:
\n' txt += '
\n' for x, y in zip(RUNES, rcount): txt += f'
{x}:
' if y in minmax: txt += '' txt += str(y) if y in minmax: txt += '' txt += '
' txt += '
\n' txt += self.pick_ngrams(runes, 2, limit=100) txt += self.pick_ngrams(runes, 3, limit=50) txt += self.pick_ngrams(runes, 4, limit=25) txt += '
\n' return txt def sec_double_rune(self, indices): txta = '
Double Runes:
\n
\n' txtb = '
Rune Difference:
\n
\n' for i, (a, b) in enumerate(zip(indices, indices[1:])): x = min(abs(a - b), min(a, b) + 29 - max(a, b)) y = 1 if x == 0 else 0 sffx = f' title="offset: {i}, rune: {RUNES[a]}"' if y else '' txta += f'{y}' txtb += f'{x}' txta += '
\n' txtb += '\n' return '
' + txta + txtb + '
\n' def sec_ioc(self, fname): trh1 = '' trh1 += 'IoC-high' trh1 += 'IoC-norm' trh1 += 'Runes / keylen' trh2 = '' trbest = 'best' trd = [f'{x}' for x in range(33)] scores = None for scores, pmin, pmax in self.score: for irp in [0, 28]: maxscore = 0 bestkl = -1 try: klarr = scores[fname][irp] except KeyError: continue trh2 += f'
{RUNES[irp]}
' for kl, (score, _) in enumerate(klarr): if score < 0: trd[kl] += f'–' else: trd[kl] += f'{score:.2f}' if score > maxscore: maxscore = score bestkl = kl trbest += f'{bestkl}' db_indices = InterruptIndices() for irp in [0, 28]: try: klarr = scores[fname][irp] except KeyError: continue trh2 += f'
{RUNES[irp]}
' for kl, (_, maxirp) in enumerate(klarr): if maxirp > 0: _, num = db_indices.consider(fname, irp, maxirp) num /= kl trd[kl] += f'{int(num)}' trh1 += '\n' trh2 += '\n' trbest += '\n' for i in range(len(trd)): trd[i] += '\n' return f'{trh1}{trh2}{"".join(trd[1:])}{trbest}
' def sec_ioc_flow(self, indices, width): txt = f'
Window size {width}:
\n' txt += '
\n' for i in range(len(indices) - width + 1): ioc = Probability(indices[i:i + width]).IC() clss = mark(ioc, HIGH_MIN - 0.1, HIGH_MAX) txt += f'{ioc:.2f}' txt += '
\n' return txt def pick_letters(self, words, idx, desc): letters = [] for x in words: letters.append(x[idx]) ioc = Probability(x.index for x in letters).IC() txt = f'
Pick every {desc} letter (IoC: {ioc:.3f}):
\n' txt += '
\n' for x in letters: txt += f'
{x.text}
' txt += '
\n' return txt def pick_words(self, words, n): txt = '' for u in range(n): if n > 1: txt += f'

Start with {u + 1}. word

\n' subset = [x for x in words[u:None:n]] ioc = Probability(x.index for y in subset for x in y).IC() txt += f'
Words (IoC: {ioc:.3f}):
\n' txt += '
\n' for x in subset: txt += str(x.text) + ' ' txt += '
\n' txt += self.pick_letters(subset, 0, 'first') txt += self.pick_letters(subset, -1, 'last') return txt def sec_concealment(self, words): txt = '' for n in range(1, 6): txt += f'

Pick every {n}. word

\n' txt += '
\n' txt += self.pick_words(words, n) txt += '
\n' return txt def make(self, fname, outfile): source = RuneTextFile(LPath.page(fname)) words = [x[3] for x in source.enum_words()] runes = [x for w in words for x in w] indices = [x.index for x in runes] html = self.template.replace('__FNAME__', fname) html = html.replace('__SEC_COUNTS__', self.sec_counts(words, runes)) html = html.replace('__SEC_DOUBLE__', self.sec_double_rune(indices)) if fname.startswith('solved_'): warn = '

IoC is disabled on solved pages. Open the ' warn += f'“unsolved” page' warn += ' instead.

' html = html.replace('__SEC_IOC__', warn) else: html = html.replace('__SEC_IOC__', self.sec_ioc(fname)) ioc_flow = '
\n' for winsize in [120, 80, 50, 30, 20]: ioc_flow += self.sec_ioc_flow(indices, winsize) ioc_flow += '
\n' html = html.replace('__SEC_IOC_FLOW__', ioc_flow) html = html.replace('__SEC_CONCEAL__', self.sec_concealment(words)) with open(LPath.results(outfile), 'w') as f: f.write(html) class IndexToWeb(object): def __init__(self, template='templates/index.html'): with open(LPath.results(template), 'r') as f: self.template = f.read() def make(self, the_links, outfile='index.html'): html = self.template for key, links in the_links.items(): txt = '' for x, y in links: txt += f' {y} ' html = html.replace(key, txt) with open(LPath.results(outfile), 'w') as f: f.write(html) if __name__ == '__main__': links = { '__A_IOC__': [('ioc/high.html', 'Highest (bluntly)'), ('ioc/norm.html', 'Normal english (1.7767)')], '__A_CHAPTER__': [(f'pages/{x}.html', x) for x in FILES_ALL], '__A_SOLVED__': [(f'pages/solved_{x}.html', x) for x in FILES_SOLVED] } InterruptToWeb('db_high').make('ioc/high.html', HIGH_MIN, HIGH_MAX) InterruptToWeb('db_norm').make('ioc/norm.html', NORM_MIN, NORM_MAX) ctw = ChapterToWeb() for x, y in links['__A_CHAPTER__']: ctw.make(y, x) for x, y in links['__A_SOLVED__']: ctw.make('solved_' + y, x) IndexToWeb().make(links)