Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions fixit/base64utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Base64 text encoding and decoding utilities."""

import base64
import binascii


def encode_text(text: str) -> str:
"""Encode UTF-8 text as a Base64 string."""
encoded = base64.b64encode(text.encode("utf-8"))
return encoded.decode("ascii")


def decode_text(text: str) -> str:
"""Decode a Base64 string into UTF-8 text."""
try:
encoded = text.encode("ascii")
decoded = base64.b64decode(encoded, validate=True)
return decoded.decode("utf-8")
except (binascii.Error, UnicodeEncodeError, UnicodeDecodeError) as exc:
raise ValueError("Invalid Base64 input.") from exc
47 changes: 43 additions & 4 deletions fixit/cli.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@

"""Command line entry point: `fixit <command>`."""
import argparse
import sys
import uuid
from pathlib import Path

from fixit import __version__, convert, jsonfmt, password, textutils, wordcount
from fixit import (
__version__,
base64utils,
convert,
jsonfmt,
password,
textutils,
wordcount,
)


CONVERSIONS = {
"c2f": convert.celsius_to_fahrenheit,
Expand Down Expand Up @@ -81,6 +91,7 @@ def run_slugify(args):
"json": run_json,
"palindrome": run_palindrome,
"reverse": run_reverse,
"base64": run_base64,
"slugify": run_slugify,
"top-words": run_top_words,
}
Expand All @@ -94,8 +105,15 @@ def build_parser() -> argparse.ArgumentParser:
args = parser.parse_args(["wc", "hello world"])
assert args.command == "wc"
"""
parser = argparse.ArgumentParser(prog="fixit", description="A tiny toolbox to practice open source.")
parser.add_argument("--version", action="version", version=f"fixit {__version__}")
parser = argparse.ArgumentParser(
prog="fixit",
description="A tiny toolbox to practice open source.",
)
parser.add_argument(
"--version",
action="version",
version=f"fixit {__version__}",
)
sub = parser.add_subparsers(dest="command", required=True)

wc = sub.add_parser("wc", help="count words, lines and characters")
Expand All @@ -122,7 +140,28 @@ def build_parser() -> argparse.ArgumentParser:
rw = sub.add_parser("reverse", help="reverse the words in a sentence")
rw.add_argument("text")

sg = sub.add_parser("slugify", help="convert text into a URL-friendly slug")
b64 = sub.add_parser("base64", help="encode or decode Base64 text")
b64_sub = b64.add_subparsers(
dest="base64_action",
required=True,
)

b64_encode = b64_sub.add_parser(
"encode",
help="encode text as Base64",
)
b64_encode.add_argument("text")

b64_decode = b64_sub.add_parser(
"decode",
help="decode Base64 text",
)
b64_decode.add_argument("text")

sg = sub.add_parser(
"slugify",
help="convert text into a URL-friendly slug",
)
sg.add_argument("text")

top = sub.add_parser("top-words", help="show the most frequent words")
Expand Down
43 changes: 43 additions & 0 deletions tests/test_base64.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import pytest

from fixit import base64utils, cli


def test_encode_text():
assert base64utils.encode_text("hello") == "aGVsbG8="


def test_decode_text():
assert base64utils.decode_text("aGVsbG8=") == "hello"


def test_encode_unicode_text():
text = "\u0928\u092e\u0938\u094d\u0924\u0947"
assert base64utils.decode_text(base64utils.encode_text(text)) == text


def test_empty_text():
assert base64utils.encode_text("") == ""
assert base64utils.decode_text("") == ""


@pytest.mark.parametrize("value", ["%%%", "aGVsbG8", "////"])
def test_invalid_base64_or_non_utf8_text(value):
with pytest.raises(ValueError, match="Invalid Base64 input"):
base64utils.decode_text(value)


def test_cli_encode(capsys):
assert cli.main(["base64", "encode", "hello"]) == 0
assert capsys.readouterr().out == "aGVsbG8=\n"


def test_cli_decode(capsys):
assert cli.main(["base64", "decode", "aGVsbG8="]) == 0
assert capsys.readouterr().out == "hello\n"


def test_cli_invalid_base64(capsys):
assert cli.main(["base64", "decode", "%%%"]) == 1
captured = capsys.readouterr()
assert "Invalid Base64 input" in captured.err