|
1 | | -# -*- coding: UTF-8 -*- |
2 | | -"""Tap code - Tap/knock code encoding. |
3 | | -
|
4 | | -The tap code (or knock code), used by prisoners of war (e.g. in Vietnam) to communicate by tapping on walls, writes each |
5 | | - letter as its row and column in a 5x5 Polybius square, K being replaced by C. Each coordinate is written as dots (taps) |
6 | | - separated by a space, the letters being separated by the blank braille pattern ; the "-inv" variant uses the reversed |
7 | | - alphabet. |
8 | | -
|
9 | | -This codec: |
10 | | -- en/decodes strings from str to str |
11 | | -- en/decodes strings from bytes to bytes |
12 | | -- decodes file content to str (read) |
13 | | -- encodes file content from str to bytes (write) |
14 | | -
|
15 | | -Reference: https://en.wikipedia.org/wiki/Tap_code |
16 | | -""" |
17 | | -from ..__common__ import * |
18 | | - |
19 | | - |
20 | | -__examples__ = { |
21 | | - 'enc(tap|knock-code|tap_code)': {'this is a test' : ".... ....⠀.. ...⠀.. ....⠀.... ...⠀ ⠀.. ....⠀.... ...⠀ ⠀. ." |
22 | | - "⠀ ⠀.... ....⠀. .....⠀.... ...⠀.... ...."}, |
23 | | -} |
24 | | -__guess__ = ["tap", "tap-inv"] |
25 | | - |
26 | | - |
27 | | -def __build_encmap(a): |
28 | | - d, i = {}, 0 |
29 | | - for x in range(1,6): |
30 | | - for y in range(1,6): |
31 | | - d[a[i]] = x * "." + " " + y * "." |
32 | | - i += 1 |
33 | | - d['k'], d[' '] = d['c'], " " |
34 | | - return d |
35 | | - |
36 | | - |
37 | | - |
38 | | -ENCMAP = { |
39 | | - '': __build_encmap("abcdefghijlmnopqrstuvwxyz"), |
40 | | - 'inv': __build_encmap("abcdefghijlmnopqrstuvwxyz"[::-1]), |
41 | | -} |
42 | | - |
43 | | - |
44 | | -add_map("tap", ENCMAP, ignore_case="both", sep="⠀", pattern=r"^(?:tap|knock)(?:[-_]code)?(|inv)$") |
45 | | - |
| 1 | +# -*- coding: UTF-8 -*- |
| 2 | +"""Tap code - Tap/knock code encoding. |
| 3 | +
|
| 4 | +The tap code (or knock code), used by prisoners of war (e.g. in Vietnam) to communicate by tapping on walls, writes each |
| 5 | + letter as its row and column in a 5x5 Polybius square, K being replaced by C. Each coordinate is written as dots |
| 6 | + (taps), as cryptii does: the row and the column are separated by a space and the letters by two spaces. The words are |
| 7 | + separated by " / " ; the "-inv" variant uses the reversed alphabet. |
| 8 | +
|
| 9 | +When decoding, the dots are read in pairs whatever separates them (e.g. spaces, commas or the blank braille pattern, as |
| 10 | + written by former versions of this codec), a "/" (or a space between blank braille patterns) giving a space. |
| 11 | +
|
| 12 | +This codec: |
| 13 | +- en/decodes strings from str to str |
| 14 | +- en/decodes strings from bytes to bytes |
| 15 | +- decodes file content to str (read) |
| 16 | +- encodes file content from str to bytes (write) |
| 17 | +
|
| 18 | +Reference: https://en.wikipedia.org/wiki/Tap_code |
| 19 | + https://cryptii.com/pipes/tap-code |
| 20 | +""" |
| 21 | +from ..__common__ import * |
| 22 | + |
| 23 | + |
| 24 | +__examples__ = { |
| 25 | + 'enc(tap|knock-code|tap_code)': { |
| 26 | + 'this is a test': ".... .... .. ... .. .... .... ... / .. .... .... ... / . . / .... .... . ..... " |
| 27 | + ".... ... .... ....", |
| 28 | + 'water': "..... .. . . .... .... . ..... .... ..", |
| 29 | + 'Kick': ". ... .. .... . ... . ...", |
| 30 | + 'test!': None, |
| 31 | + }, |
| 32 | + 'dec(tap)': { |
| 33 | + # Wikipedia example, with commas |
| 34 | + '..... .., . ., .... ...., . ....., .... ..': "water", |
| 35 | + # as written by former versions of this codec |
| 36 | + ".... ....⠀.. ...⠀.. ....⠀.... ...⠀ ⠀.. ....⠀.... ...": "this is", |
| 37 | + '/ . . // . .. /': " a b ", |
| 38 | + '. . .': None, |
| 39 | + '. ......': None, |
| 40 | + '. / .': None, |
| 41 | + '. . x': None, |
| 42 | + }, |
| 43 | + 'enc-dec(tap|tap-inv)': ["this is a test", "the quicc brown fox jumps over the lazy dog", " ", ""], |
| 44 | +} |
| 45 | +__guess__ = ["tap", "tap-inv"] |
| 46 | + |
| 47 | + |
| 48 | +ALPHABETS = {'': "abcdefghijlmnopqrstuvwxyz", 'inv': "abcdefghijlmnopqrstuvwxyz"[::-1]} |
| 49 | + |
| 50 | + |
| 51 | +def tap_encode(variant=""): |
| 52 | + alphabet = ALPHABETS[variant] |
| 53 | + def encode(text, errors="strict"): |
| 54 | + _h, tokens = handle_error("tap", errors, sep=" "), [] |
| 55 | + for i, c in enumerate(ensure_str(text).lower()): |
| 56 | + if c == " ": |
| 57 | + tokens.append("/") |
| 58 | + elif (c := "c" if c == "k" else c) in alphabet: |
| 59 | + r, c = divmod(alphabet.index(c), 5) |
| 60 | + tokens.append("." * (r + 1) + " " + "." * (c + 1)) |
| 61 | + elif (t := _h(c, i).rstrip()): |
| 62 | + tokens.append(t) |
| 63 | + # two spaces between letters, one around the word separators |
| 64 | + r = "".join(("" if i == 0 else " " if "/" in (p, t) else " ") + t for i, (p, t) in |
| 65 | + enumerate(zip([None] + tokens, tokens))) |
| 66 | + return r, len(text) |
| 67 | + return encode |
| 68 | + |
| 69 | + |
| 70 | +def tap_decode(variant=""): |
| 71 | + alphabet = ALPHABETS[variant] |
| 72 | + def decode(text, errors="strict"): |
| 73 | + _h, t = handle_error("tap", errors, decode=True, kind="token"), ensure_str(text) |
| 74 | + # alternating separators and runs of dots, starting and ending with a (possibly empty) separator |
| 75 | + parts, r, pos, row = re.split(r"(\.+)", t), "", 0, None |
| 76 | + for k, p in enumerate(parts): |
| 77 | + if k % 2: |
| 78 | + if len(p) > 5: |
| 79 | + r += _h(p, pos) |
| 80 | + elif row is None: |
| 81 | + row = len(p) - 1 |
| 82 | + else: |
| 83 | + r, row = r + alphabet[row * 5 + len(p) - 1], None |
| 84 | + else: |
| 85 | + for i, c in enumerate(p): |
| 86 | + if not (c.isspace() or c in ",/⠀"): |
| 87 | + r += _h(c, pos + i) |
| 88 | + # a "/" or, as with former versions of this codec, a space between blank braille patterns is a space |
| 89 | + n = p.count("/") if "/" in p else p.count(" ") if "⠀" in p else 0 |
| 90 | + if n and row is not None: |
| 91 | + # a space cannot separate the row and the column of a letter |
| 92 | + r += _h(p, pos) |
| 93 | + row = None |
| 94 | + r += " " * n |
| 95 | + pos += len(p) |
| 96 | + if row is not None: |
| 97 | + r += _h(parts[-2], pos - len(parts[-1]) - len(parts[-2])) |
| 98 | + return r, len(text) |
| 99 | + return decode |
| 100 | + |
| 101 | + |
| 102 | +add("tap", tap_encode, tap_decode, r"^(?:tap|knock)(?:[-_]code)?(?:[-_]?(inv))?$", printables_rate=1., |
| 103 | + expansion_factor=(7.5, .04)) |
0 commit comments