#!/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''
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)