Skip to content

Commit f0e8d2e

Browse files
committed
Added new codec: letter-indices
1 parent 5733e54 commit f0e8d2e

5 files changed

Lines changed: 130 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ o
187187
`gray` | text <-> gray encoded text | aka reflected binary code
188188
`html` | text <-> HTML entities | implements entities according to [this reference](https://dev.w3.org/html5/html-author/charref)
189189
`leetspeak` | text <-> leetspeak encoded text | based on minimalistic elite speaking rules
190+
`letter-indices` | text <-> text with letter indices | encodes consonants and/or vowels with their corresponding indices
190191
`manchester` | text <-> manchester encoded text | XORes each bit of the input with `01`
191192
`markdown` | markdown --> HTML | unidirectional
192193
`morse` | text <-> morse encoded text | uses whitespace as a separator

codext/others/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: UTF-8 -*-
22
from .dna import *
33
from .html import *
4+
from .letters import *
45
from .markdown import *
56
from .url import *
67

codext/others/letters.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# -*- coding: UTF-8 -*-
2+
"""Letters Codec - letter indices-related content encoding.
3+
4+
This codec:
5+
- en/decodes strings from str to str
6+
- en/decodes strings from bytes to bytes
7+
- decodes file content to str (read)
8+
- encodes file content from str to bytes (write)
9+
"""
10+
from string import ascii_uppercase
11+
12+
from ..__common__ import *
13+
14+
15+
__examples__ = {
16+
'enc(consonant-index|consonants_indices)': {
17+
'This is a test': "166I15I15A16E1516",
18+
'\x00': None,
19+
'\xff': None,
20+
},
21+
'dec(consonant-index|consonants_indices)': {
22+
'166I15I15A16E1516': "THISISATEST",
23+
'\x00': None,
24+
'\xff': None,
25+
},
26+
'enc(vowel-index|vowels_indices)': {'This is a test': "TH3S3S1T2ST"},
27+
'dec(vowel-index|vowels_indices)': {'TH3S3S1T2ST': "THISISATEST"},
28+
'enc(consonant-vowel_indices)': {'This is a test': "C16C6V3C15V3C15V1C16V2C15C16"},
29+
'dec(consonants_vowels-index)': {'C16C6V3C15V3C15V1C16V2C15C16': "THISISATEST"},
30+
}
31+
32+
33+
VOWELS = "AEIOUY"
34+
35+
36+
def __get_encmap(letters):
37+
if re.match(r"^consonants?$", letters):
38+
encmap = {c: str(i+1) for i, c in enumerate(sorted(set(ascii_uppercase) - set(VOWELS)))}
39+
for c in VOWELS:
40+
encmap[c] = c
41+
elif re.match(r"^vowels?$", letters):
42+
encmap = {c: c for c in ascii_uppercase}
43+
for i, c in enumerate(VOWELS):
44+
encmap[c] = str(i+1)
45+
elif re.match(r"^consonants?[-_]vowels?$", letters):
46+
encmap = {c: "C" + str(i+1) for i, c in enumerate(sorted(set(ascii_uppercase) - set(VOWELS)))}
47+
for i, c in enumerate(VOWELS):
48+
encmap[c] = "V" + str(i+1)
49+
for c in " ":
50+
encmap[c] = ""
51+
return encmap
52+
53+
54+
def letters_encode(letters):
55+
encmap = __get_encmap(letters)
56+
def encode(text, errors="strict"):
57+
s = ""
58+
for i, c in enumerate(text.upper()):
59+
try:
60+
s += encmap[c]
61+
except KeyError:
62+
s += handle_error(letters + "_indices", errors)(c, i)
63+
return "".join(encmap.get(c.upper(), c) for c in text), len(text)
64+
return encode
65+
66+
67+
def letters_decode(letters):
68+
decmap = {v: k for k, v in __get_encmap(letters).items()}
69+
maxlen = max(len(x) for x in decmap.keys())
70+
def decode(text, errors="strict"):
71+
s, i = "", 0
72+
while i < len(text):
73+
err = True
74+
for j in range(maxlen, 0, -1):
75+
try:
76+
s += decmap[text[i:i+j]]
77+
i += j
78+
err = False
79+
break
80+
except (IndexError, KeyError):
81+
pass
82+
if err:
83+
s += handle_error(letters + "_indices", errors, decode=True)(text[i], i)
84+
return s, len(text)
85+
return decode
86+
87+
88+
add("letter-indices", letters_encode, letters_decode,
89+
pattern=r"^(consonants?|vowels?|consonants?[-_]vowels?)[-_]ind(?:ex|ices)$")
90+

docs/enc/others.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,39 @@ This implements the full list of characters available at [this reference](https:
4646

4747
-----
4848

49+
### Letter indices
50+
51+
This encodes consonants and/or vowels with their respective indices. This codec is case insensitive, strips white spaces and only applies to letters.
52+
53+
**Codec** | **Conversions** | **Aliases** | **Comment**
54+
:---: | :---: | --- | ---
55+
`consonant-indices` | text <-> text with consonant indices | `consonants_indices`, `consonants_index` | while decoding, searches from the longest match, possibly not producing the original input
56+
`vowel-indices` | text <-> text with vowel indices | `vowels_indices`, `vowels_index` |
57+
`consonant-vowel-indices` | text <-> text with consonant and vowel indices | `consonants-vowels_index` | prefixes consonants with `C` and vowels with `V`
58+
59+
```python
60+
>>> codext.encode("This is a test", "consonant-index")
61+
'166I15I15A16E1516'
62+
>>> codext.decode("166I15I15A16E1516", "consonant-index")
63+
'THISISATEST'
64+
```
65+
66+
```python
67+
>>> codext.encode("This is a test", "vowel-index")
68+
'TH3S3S1T2ST'
69+
>>> codext.decode("TH3S3S1T2ST", "vowel-index")
70+
'THISISATEST'
71+
```
72+
73+
```python
74+
>>> codext.encode("This is a test", "consonant-vowel-index")
75+
'C16C6V3C15V3C15V1C16V2C15C16'
76+
>>> codext.decode("C16C6V3C15V3C15V1C16V2C15C16", "consonant-vowel-index")
77+
'THISISATEST'
78+
```
79+
80+
-----
81+
4982
### Markdown
5083

5184
This is only for "encoding" (converting) Markdown to HTML.

tests/test_generated.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,13 @@ class GeneratedTestCase(TestCase):
120120
error = False
121121
break
122122
except (LookupError, ValueError):
123+
123124
error = True
124125
if error:
125-
continue
126+
try:
127+
ci = lookup(examples(encoding, 1)[0])
128+
except LookupError:
129+
continue
126130
# only consider codecs with __examples__ defined in their globals for dynamic tests generation
127131
if ci.parameters.get('examples') is not None:
128132
f = make_test(**ci.parameters)

0 commit comments

Comments
 (0)