Compare commits

..

No commits in common. "master" and "master" have entirely different histories.

11 changed files with 327 additions and 152 deletions

View file

@ -1,13 +1,13 @@
=======
Credits
=======
Development Lead
================
----------------
* itec <itec@ventuordici.org>
Contributors
============
------------
* Autoscatto
* Baku
* Boyska
* Encrypt
* Thegamer
None yet. Why not be the first?

View file

@ -9,16 +9,14 @@ banana
Features
--------
- Supports multiple, super-interesting bases: banana 🍌, ananas 🍍, ribes 🍇 avocado 🥑
- Supports multiple, super-interesting bases: banana |:banana:|, ananas |:pineapple:|, ribes |:grapes:|, avocado |:avocado:|
- Encode and decode easily
- Check validity of encoded words
- Generate random words 🎲
- It's easy to support additional bases based on the same principles 🧩
- Generate random words |:game_die:|
- It's easy to support additional bases based on the same principles |:jigsaw:|
Credits
-------
Thanks to Autoscatto, Baku, Boyska for suggestions and developement.
Special thanks to Stefano Bartezzaghi.
Loosely inspired by Stefano Bartezzaghi's book "Accavallavacca. Inventario di parole da gioco", Milano, Bompiani, 1992. ISBN 88-452-1948-8.
All hail Stefano Bartezzaghi

View file

@ -1,5 +1,5 @@
"""Top-level package for banana."""
from .libbanana import *
from .bananalib import *
__author__ = """itec"""
__email__ = "itec@ventuordici.org"

104
banana/bananalib.py Normal file
View file

@ -0,0 +1,104 @@
"""Main module."""
import logging
import random
log = logging.getLogger("bananalib")
class Codec:
def __init__(self, dictstart=0, shiftend=0, minlength=0, dictionary=None):
self.dictstart = dictstart
self.shiftend = shiftend
if dictionary is None:
self.dictionary = [list("bcdfglmnprstvz"), list("aeiou")]
else:
self.dictionary = dictionary
def encode(self, num, minlength=0):
dictionary = self.dictionary
numdict = len(dictionary)
v = num
st = ""
length = 0
idx = (numdict - 1 + self.dictstart + self.shiftend) % numdict
while not (
v == 0
and idx == (numdict - 1 + self.dictstart) % numdict
and length >= minlength
):
r = v % len(dictionary[idx])
v = int(v / len(dictionary[idx]))
st = dictionary[idx][r] + st
idx = (idx - 1) % numdict
length += 1
return st
def decode(self, word):
dictionary = self.dictionary
numdict = len(dictionary)
if (len(word) - self.shiftend) % numdict != 0:
raise ValueError("Banana non valida")
v = 0
for i in range(len(word)):
r = (numdict + i + self.dictstart) % numdict
try:
v = v * len(dictionary[r]) + dictionary[r].index(word[i])
except (ValueError, KeyError):
raise ValueError("Carattere non valido in posizione %d" % i + 1)
return v
def is_valid(self, word):
dictionary = self.dictionary
numdict = len(dictionary)
if (len(word) - self.shiftend) % numdict != 0:
return False
for i in range(len(word)):
r = (numdict + i + self.dictstart) % numdict
if word[i] not in dictionary[r]:
return False
return True
def random(self, minlength=6, prng=random.Random()):
numdict = len(self.dictionary)
word = ""
if minlength < 1:
return ""
curr_dict = (numdict - 1 + self.dictstart + self.shiftend) % numdict
final_dict = (numdict - 1 + self.dictstart) % numdict
while curr_dict != final_dict or len(word) < minlength:
word = prng.choice(self.dictionary[curr_dict]) + word
curr_dict = (curr_dict - 1) % numdict
return word
class BananaCodec(Codec):
def __init__(self):
super().__init__()
class RibesCodec(Codec):
def __init__(self):
super().__init__(0, 1)
class AnanasCodec(Codec):
def __init__(self):
super().__init__(1, 0)
class AvocadoCodec(Codec):
def __init__(self):
super().__init__(1, 1)
if __name__ == "__main__":
print("Ciao sono la libreria banana")

View file

@ -8,22 +8,26 @@ import banana
def get_codec(args):
if args.banana:
return banana.BananaCodec()
if args.ananas:
return banana.AnanasCodec()
if args.ribes:
return banana.RibesCodec()
if args.avocado:
return banana.AvocadoCodec()
kwargs = {}
if args.alphabets:
kwargs["alphabets"] = args.alphabets
if args.shiftalpha:
kwargs["shiftalpha"] = args.shiftalpha
if args.alphaend:
kwargs["alphaend"] = args.alphaend
if args.dictionary:
kwargs["dictionary"] = args.dictionary
if args.dictstart:
kwargs["dictstart"] = args.dictstart
if args.shiftend:
kwargs["shiftend"] = args.shiftend
return banana.Codec(**kwargs)
def main_encode(args):
codec = get_codec(args)
kwargs = dict(num=args.num)
if args.minlength:
kwargs["minlength"] = args.minlength
print(codec.encode(**kwargs))
print(get_codec(args).encode(args.num))
def main_decode(args):
@ -54,38 +58,41 @@ def colon_separated_list(s):
def main():
parser = argparse.ArgumentParser(description="Convert number to banana")
parser = argparse.ArgumentParser(description="Convert dec number to banana")
parser.add_argument(
"--log-level", choices=["DEBUG", "INFO", "WARN", "ERROR"], default="WARN"
)
parser.add_argument("--ananas", action="store_true")
parser.add_argument("--avocado", action="store_true")
parser.add_argument("--banana", action="store_true")
parser.add_argument("--ribes", action="store_true")
parser.add_argument(
"--alphabets", "-a",
help="Set alphabets in colon-separated list",
"--dictionary",
help="Set dictionary in colon-separated list",
type=colon_separated_list,
)
parser.add_argument(
"--shiftalpha", "-s", help="Set shift for alphabets", type=int, default=0
"--dictstart", help="Set starting dictionary", type=int, default=0
)
parser.add_argument(
"--alphaend", "-e", help="Set ending alphabet", type=int, default=0
"--shiftend", help="Set shift for ending dictionary", type=int, default=0
)
sub = parser.add_subparsers()
encode = sub.add_parser("encode", help="Convert number to word")
encode = sub.add_parser("encode", help="Convert numbers to words")
encode.add_argument("num", type=int)
encode.add_argument("--minlength", "-l", help="Set minimum length", type=int, default=1)
encode.set_defaults(func=main_encode)
decode = sub.add_parser("decode", help="Convert word to number")
decode = sub.add_parser("decode", help="Convert words to numbers")
decode.add_argument("word")
decode.set_defaults(func=main_decode)
check = sub.add_parser("check", help="Check if word is banana")
check = sub.add_parser("check", help="Convert words to numbers")
check.add_argument("word")
check.add_argument("--quiet", "-q", action="store_true")
check.set_defaults(func=main_check)
rand = sub.add_parser("random", help="Generate random banana")
rand.add_argument("--minlength", "-l", help="Set minimum length", type=int, default=6)
rand.add_argument("--minlength", help="Set minimum length", type=int, default=6)
rand.add_argument("--seed", type=int, default=None)
rand.set_defaults(func=main_random)

View file

@ -1,89 +0,0 @@
"""Main module."""
import logging
import random
log = logging.getLogger("libbanana")
class Codec:
def __init__(self, shiftalpha=0, alphaend=0, minlength=0, alphabets=None):
self.shiftalpha = shiftalpha
self.alphaend = alphaend
if alphabets is None:
self.alphabets = [list("bcdfglmnprstvz"), list("aeiou")]
else:
self.alphabets = alphabets
def encode(self, num, minlength=1):
alphabets = self.alphabets
numalpha = len(alphabets)
v = num
st = ""
length = 0
idx = (numalpha - 1 + self.shiftalpha + self.alphaend) % numalpha
while not (
v == 0
and idx == (numalpha - 1 + self.shiftalpha) % numalpha
and length >= minlength
):
r = v % len(alphabets[idx])
v = int(v / len(alphabets[idx]))
st = alphabets[idx][r] + st
idx = (idx + numalpha - 1) % numalpha
length += 1
return st
def decode(self, word):
alphabets = self.alphabets
numalpha = len(alphabets)
if (len(word) - self.alphaend) % numalpha != 0:
raise ValueError("Invalid banana")
v = 0
for i in range(len(word)):
r = (numalpha + i + self.shiftalpha) % numalpha
try:
v = v * len(alphabets[r]) + alphabets[r].index(word[i])
except (ValueError, KeyError):
raise ValueError("Invalid character in position %d" % i + 1)
return v
def is_valid(self, word):
alphabets = self.alphabets
numalpha = len(alphabets)
if (len(word) - self.alphaend) % numalpha != 0:
return False
for i in range(len(word)):
r = (numalpha + i + self.shiftalpha) % numalpha
if word[i] not in alphabets[r]:
return False
return True
def random(self, minlength=6, prng=random.Random()):
numalpha = len(self.alphabets)
word = ""
if minlength < 1:
return ""
curr_alpha = (numalpha - 1 + self.shiftalpha + self.alphaend) % numalpha
final_alpha = (numalpha - 1 + self.shiftalpha) % numalpha
while curr_alpha != final_alpha or len(word) < minlength:
word = prng.choice(self.alphabets[curr_alpha]) + word
curr_alpha = (curr_alpha - 1) % numalpha
return word
class BananaCodec(Codec):
def __init__(self):
super().__init__()
if __name__ == "__main__":
print("Hi I'm the basebanana library")

View file

@ -1,9 +1,9 @@
pip==23.2.1
pip==19.2.3
bump2version==0.5.11
wheel==0.33.6
watchdog==0.9.0
flake8==3.7.8
tox==4.11.3
tox==3.14.0
coverage==4.5.4
Sphinx==1.8.5
m2r==0.2.1
@ -11,5 +11,5 @@ twine==1.14.0
sphinxemoji==0.1.7
pytest==6.2.5
pytest==4.6.5
pytest-runner==5.1

View file

@ -22,4 +22,5 @@ exclude = docs
test = pytest
[tool:pytest]
addopts = --ignore=setup.py
collect_ignore = ['setup.py']

View file

@ -31,24 +31,25 @@ setup(
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
],
description="basebanana",
description="banana",
entry_points={
"console_scripts": [
"basebanana=banana.cli:main",
"banana=banana.cli:main",
"accavallavacca=banana.cli:main",
"bananarandom=banana.cli:bananarandom",
]
},
install_requires=requirements,
license="MIT license",
long_description=readme + "\n\n" + history,
include_package_data=True,
keywords="basebanana",
name="basebanana",
keywords="banana",
name="banana",
packages=find_packages(include=["banana", "banana.*"]),
setup_requires=setup_requirements,
test_suite="tests",
tests_require=test_requirements,
url="https://git.lattuga.net/itec/banana",
version="0.2.0",
version="0.1.0",
zip_safe=False,
)

View file

@ -5,21 +5,9 @@ import random
import pytest
from banana import BananaCodec
from banana import AnanasCodec, AvocadoCodec, BananaCodec, RibesCodec
banana_conversions = {
"be": 1,
"da": 10,
"bema" : 100,
"duga": 1000,
"bibiva": 10000,
"galopa": 100000,
"bivucasa": 1000000,
"beba": 70,
"zu": 69,
"bezu": 139,
"nana": 2485,
}
banana_conversions = {"be": 1, "beba": 70, "zu": 69, "bezu": 139, "nana": 2485}
@pytest.fixture(params=banana_conversions.items())
@ -27,9 +15,74 @@ def banana_known(request):
yield request.param
avocado_conversions = {
"a": 0,
"aca": 5,
"ada": 10,
"afa": 15,
"aga": 20,
"ala": 25,
"ama": 30,
"ana": 35,
"apa": 40,
"ara": 45,
"asa": 50,
"ata": 55,
"ava": 60,
"aza": 65,
"eba": 70,
"eca": 75,
"eda": 80,
"efa": 85,
"ega": 90,
}
@pytest.fixture(params=avocado_conversions.items())
def avocado_known(request):
yield request.param
ribes_conversions = {"b": 0, "c": 1, "z": 13, "beb": 14, "bec": 15}
@pytest.fixture(params=ribes_conversions.items())
def ribes_known(request):
yield request.param
ananas_conversions = {
"ac": 1,
"al": 5,
"as": 10,
"ec": 15,
"em": 20,
"et": 25,
"id": 30,
"in": 35,
"iv": 40,
"of": 45,
"op": 50,
"oz": 55,
"ug": 60,
"ur": 65,
"acab": 70,
"acal": 75,
"acas": 80,
"acec": 85,
"acem": 90,
}
ananas_codec = AnanasCodec()
avocado_codec = AvocadoCodec()
banana_codec = BananaCodec()
ribes_codec = RibesCodec()
@pytest.fixture(params=ananas_conversions.items())
def ananas_known(request):
yield request.param
def test_banana_to_dec_known(banana_known):
@ -46,6 +99,12 @@ def test_banana_is_banana(banana_known):
assert banana_codec.is_valid(banana_known[0])
def test_banana_is_only_banana(banana_known):
assert not ribes_codec.is_valid(banana_known[0])
assert not ananas_codec.is_valid(banana_known[0])
assert not avocado_codec.is_valid(banana_known[0])
def test_banana2dec_prefix_ba(banana_known):
"""un ba all'inizio non cambia nulla!"""
word, value = banana_known
@ -53,6 +112,66 @@ def test_banana2dec_prefix_ba(banana_known):
assert banana_codec.decode(prefix + word) == value
def test_ribes_to_dec_known(ribes_known):
word, value = ribes_known
assert ribes_codec.decode(word) == value
def test_dec_to_ribes_known(ribes_known):
word, value = ribes_known
assert ribes_codec.encode(value) == word
def test_ribes_is_ribes(ribes_known):
assert ribes_codec.is_valid(ribes_known[0])
def test_ribes_is_only_ribes(ribes_known):
assert not banana_codec.is_valid(ribes_known[0])
assert not ananas_codec.is_valid(ribes_known[0])
assert not avocado_codec.is_valid(ribes_known[0])
def test_avocado_to_dec_known(avocado_known):
word, value = avocado_known
assert avocado_codec.decode(word) == value
def test_dec_to_avocado_known(avocado_known):
word, value = avocado_known
assert avocado_codec.encode(value) == word
def test_avocado_is_avocado(avocado_known):
assert avocado_codec.is_valid(avocado_known[0])
def test_avocado_is_only_avocado(avocado_known):
assert not ribes_codec.is_valid(avocado_known[0])
assert not ananas_codec.is_valid(avocado_known[0])
assert not banana_codec.is_valid(avocado_known[0])
def test_ananas_to_dec_known(ananas_known):
word, value = ananas_known
assert ananas_codec.decode(word) == value
def test_dec_to_ananas_known(ananas_known):
word, value = ananas_known
assert ananas_codec.encode(value) == word
def test_ananas_is_ananas(ananas_known):
assert ananas_codec.is_valid(ananas_known[0])
def test_ananas_is_only_ananas(ananas_known):
assert not ribes_codec.is_valid(ananas_known[0])
assert not banana_codec.is_valid(ananas_known[0])
assert not banana_codec.is_valid(ananas_known[0])
def test_answer_to_life_the_universe_and_everything():
banana = banana_codec.decode("banana")
assert banana != 42
@ -61,4 +180,41 @@ def test_answer_to_life_the_universe_and_everything():
def test_random_len_0():
assert banana_codec.random(minlength=0) == ""
assert ananas_codec.random(minlength=0) == ""
assert avocado_codec.random(minlength=0) == ""
assert ribes_codec.random(minlength=0) == ""
def test_banana_random_minlength_even():
for l in (0, 2, 4, 6, 8, 10, 12):
assert len(banana_codec.random(minlength=l)) == l
assert len(ananas_codec.random(minlength=l)) == l
def test_banana_random_minlength_odd():
for l in (1, 3, 5, 7, 9):
assert len(banana_codec.random(minlength=l)) == l + 1
assert len(ananas_codec.random(minlength=l)) == l + 1
def test_ribes_random_minlength_even():
for l in (2, 4, 6, 8, 10, 12):
assert len(ribes_codec.random(minlength=l)) == l + 1
assert len(avocado_codec.random(minlength=l)) == l + 1
def test_ribes_random_minlength_odd():
for l in (1, 3, 5, 7, 9):
assert len(ribes_codec.random(minlength=l)) == l
assert len(avocado_codec.random(minlength=l)) == l
def test_random_coherence_please():
for codec in (ribes_codec, ananas_codec, avocado_codec, banana_codec):
for seed in range(30):
word = codec.random(prng=random.Random(seed))
assert codec.is_valid(word), "%s (seed %d) non valido per %s" % (
word,
seed,
codec.__class__.__name__,
)

View file

@ -1,11 +1,8 @@
[tox]
envlist = py35, py36, py37, py38, py39, py310, py311, flake8
envlist = py35, py36, py37, py38, flake8
[travis]
python =
3.11: py311
3.10: py310
3.9: py39
3.8: py38
3.7: py37
3.6: py36