Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdbf6396ab | ||
|
|
0957bff43a |
@@ -174,3 +174,6 @@ cython_debug/
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
|
||||
# Doxygen output
|
||||
docs/doxygen/
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
PROJECT_NAME = "Caesar Cipher"
|
||||
PROJECT_BRIEF = "Udemy 100 Days of Code - Day 8"
|
||||
OUTPUT_DIRECTORY = docs/doxygen
|
||||
INPUT = src README.md docs/index.md
|
||||
USE_MDFILE_AS_MAINPAGE = README.md
|
||||
FILE_PATTERNS = *.py *.md
|
||||
RECURSIVE = YES
|
||||
EXTRACT_ALL = YES
|
||||
OPTIMIZE_OUTPUT_JAVA = YES
|
||||
GENERATE_HTML = YES
|
||||
GENERATE_LATEX = NO
|
||||
QUIET = YES
|
||||
@@ -1,2 +1,76 @@
|
||||
# 008-caesar_cipher
|
||||
# 🔐 Caesar Cipher
|
||||
|
||||
A beginner-friendly Python program that encodes messages with the Caesar cipher.
|
||||
|
||||
## 📚 Table of Contents
|
||||
|
||||
- [Overview](#-overview)
|
||||
- [Requirements](#-requirements)
|
||||
- [Setup](#-setup)
|
||||
- [Run](#-run)
|
||||
- [Tests](#-tests)
|
||||
- [License](#-license)
|
||||
- [Links](#-links)
|
||||
|
||||
## 🧭 Overview
|
||||
|
||||
The Caesar cipher shifts every letter by a fixed number of places (with a shift of 3, `E` becomes `H`). This project, from Udemy's *100 Days of Code: The Complete Python Pro Bootcamp* (Day 8), finds letter positions with `list.index` and wraps around the alphabet with the modulo operator. Part 1 covers encryption; decryption and a combined program follow in later parts. Code lives in `src/`, tests in `tests/`, docs in `docs/`.
|
||||
|
||||
## 📋 Requirements
|
||||
|
||||
- Python 3.13 or newer
|
||||
- `pytest` (development only; no runtime dependencies)
|
||||
- Optional: [Doxygen](https://www.doxygen.nl/) to build the API docs
|
||||
|
||||
## 🛠️ Setup
|
||||
|
||||
Create and activate a local virtual environment, then upgrade pip and install the project:
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
```
|
||||
|
||||
Activate it:
|
||||
|
||||
```bash
|
||||
# Linux / macOS
|
||||
source .venv/bin/activate
|
||||
# Git Bash on Windows
|
||||
source .venv/Scripts/activate
|
||||
```
|
||||
|
||||
```powershell
|
||||
# Windows PowerShell
|
||||
.venv\Scripts\Activate.ps1
|
||||
```
|
||||
|
||||
```bash
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
## ▶️ Run
|
||||
|
||||
```bash
|
||||
python -m caesar_cipher
|
||||
```
|
||||
|
||||
or, after installation, `caesar-cipher`.
|
||||
|
||||
## 🧪 Tests
|
||||
|
||||
```bash
|
||||
python -m pytest
|
||||
```
|
||||
|
||||
Build the API docs with `doxygen Doxyfile` (output in `docs/doxygen/html`).
|
||||
|
||||
## 📄 License
|
||||
|
||||
GNU AGPL-3.0 – see [LICENSE](LICENSE).
|
||||
|
||||
## 🔗 Links
|
||||
|
||||
- [Repository](https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code)
|
||||
- [Documentation](docs/index.md)
|
||||
- [Issue tracker](https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/008-caesar_cipher/issues)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Caesar Cipher – documentation
|
||||
|
||||
Each letter is moved `shift` places through the alphabet:
|
||||
`new_position = (ALPHABET.index(letter) + shift) % 26`.
|
||||
The modulo keeps positions in range for shifts past `z`.
|
||||
Non-letters are left untouched and letter case is preserved.
|
||||
|
||||
API reference: run `doxygen Doxyfile` from the repository root and open
|
||||
`docs/doxygen/html/index.html`. The step-by-step plan is in [plan.md](plan.md).
|
||||
@@ -0,0 +1,26 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=77"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "caesar-cipher"
|
||||
version = "0.1.0"
|
||||
description = "A beginner-friendly Caesar cipher encoder/decoder (Udemy 100 Days of Code, Day 8)."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
license = "AGPL-3.0-or-later"
|
||||
license-files = ["LICENSE"]
|
||||
dependencies = []
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest>=8"]
|
||||
|
||||
[project.scripts]
|
||||
caesar-cipher = "caesar_cipher.main:main"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = ["src"]
|
||||
testpaths = ["tests"]
|
||||
@@ -0,0 +1,6 @@
|
||||
## @file __init__.py
|
||||
# @brief Public API of the caesar_cipher package.
|
||||
|
||||
from caesar_cipher.cipher import encrypt
|
||||
|
||||
__all__ = ["encrypt"]
|
||||
@@ -0,0 +1,6 @@
|
||||
## @file __main__.py
|
||||
# @brief Enables `python -m caesar_cipher`.
|
||||
|
||||
from caesar_cipher.main import main
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,21 @@
|
||||
## @file cipher.py
|
||||
# @brief Caesar cipher encryption.
|
||||
|
||||
from caesar_cipher.constants import ALPHABET
|
||||
|
||||
|
||||
def encrypt(text: str, shift: int) -> str:
|
||||
## @brief Encrypt a text with the Caesar cipher.
|
||||
# @param text Original text.
|
||||
# @param shift Positions to move each letter (may be negative or > 26).
|
||||
# @return The encrypted text; non-letters are kept, case is preserved.
|
||||
result = []
|
||||
for char in text:
|
||||
lower = char.lower()
|
||||
if lower in ALPHABET:
|
||||
new_position = (ALPHABET.index(lower) + shift) % len(ALPHABET)
|
||||
new_char = ALPHABET[new_position]
|
||||
result.append(new_char.upper() if char.isupper() else new_char)
|
||||
else:
|
||||
result.append(char)
|
||||
return "".join(result)
|
||||
@@ -0,0 +1,13 @@
|
||||
## @file constants.py
|
||||
# @brief Constants used by the Caesar cipher.
|
||||
|
||||
## Lowercase alphabet used to look up letter positions.
|
||||
ALPHABET = list("abcdefghijklmnopqrstuvwxyz")
|
||||
|
||||
## Prompt for the message.
|
||||
PROMPT_TEXT = "Type your message:\n"
|
||||
## Prompt for the shift number.
|
||||
PROMPT_SHIFT = "Type the shift number:\n"
|
||||
|
||||
## Name of the environment file holding tokens.
|
||||
ENV_FILE = ".env"
|
||||
@@ -0,0 +1,31 @@
|
||||
## @file env.py
|
||||
# @brief Minimal stdlib `.env` reader (no third-party dependency).
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from caesar_cipher.constants import ENV_FILE
|
||||
|
||||
|
||||
def load_env(path: str | Path = ENV_FILE) -> dict[str, str]:
|
||||
## @brief Read KEY=VALUE pairs from an env file.
|
||||
# @param path Path of the env file.
|
||||
# @return Mapping of variables; empty if the file does not exist.
|
||||
values: dict[str, str] = {}
|
||||
env_path = Path(path)
|
||||
if not env_path.is_file():
|
||||
return values
|
||||
for line in env_path.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, _, value = line.partition("=")
|
||||
values[key.strip()] = value.strip().strip("\"'")
|
||||
return values
|
||||
|
||||
|
||||
def get_token(name: str, path: str | Path = ENV_FILE) -> str | None:
|
||||
## @brief Get a token from the process environment or the env file.
|
||||
# @param name Variable name, e.g. GITEA_TOKEN.
|
||||
# @return The token or None when not set.
|
||||
return os.environ.get(name) or load_env(path).get(name)
|
||||
@@ -0,0 +1,20 @@
|
||||
## @file main.py
|
||||
# @brief Interactive command line interface.
|
||||
|
||||
from caesar_cipher import constants as c
|
||||
from caesar_cipher.cipher import encrypt
|
||||
|
||||
|
||||
def main() -> None:
|
||||
## @brief Ask for a message and a shift, then print the encrypted text.
|
||||
text = input(c.PROMPT_TEXT)
|
||||
try:
|
||||
shift = int(input(c.PROMPT_SHIFT))
|
||||
except ValueError:
|
||||
print("The shift must be a whole number.")
|
||||
return
|
||||
print(f"Here's the encoded result: {encrypt(text, shift)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,26 @@
|
||||
from caesar_cipher import encrypt
|
||||
from caesar_cipher.env import load_env
|
||||
|
||||
|
||||
def test_encrypt_basic():
|
||||
assert encrypt("hello", 5) == "mjqqt"
|
||||
|
||||
|
||||
def test_wraps_around_alphabet():
|
||||
assert encrypt("xyz", 3) == "abc"
|
||||
assert encrypt("abc", 29) == "def"
|
||||
|
||||
|
||||
def test_non_letters_and_case_preserved():
|
||||
assert encrypt("Hi there, 42!", 1) == "Ij uifsf, 42!"
|
||||
|
||||
|
||||
def test_negative_shift():
|
||||
assert encrypt("def", -3) == "abc"
|
||||
|
||||
|
||||
def test_load_env(tmp_path):
|
||||
f = tmp_path / ".env"
|
||||
f.write_text("# c\nA=1\nB='two'\n", encoding="utf-8")
|
||||
assert load_env(f) == {"A": "1", "B": "two"}
|
||||
assert load_env(tmp_path / "missing") == {}
|
||||
Reference in New Issue
Block a user