{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cell-23-09-00",
   "metadata": {},
   "source": [
    "# 23-09 · Сканируем настоящий каталог\n",
    "\n",
    "Практика к разделу [«Сканируем каталог»](../../site/chapters/glava-23/23-08-skaniruem-katalog.html). Использует настоящий пакет `safesort` (`projects/python/safesort/`)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "setup-23-09",
   "metadata": {},
   "source": [
    "## Reproducible local environment\n",
    "\n",
    "```bash\n",
    "git clone https://github.com/Cartesian-School/safesort.git\n",
    "cd safesort\n",
    "python3.14 -m venv .venv\n",
    "source .venv/bin/activate\n",
    "# Windows PowerShell: .venv\\Scripts\\Activate.ps1\n",
    "python -m pip install -U pip\n",
    "python -m pip install -e \".[dev]\"\n",
    "python -m pip install jupyter ipykernel\n",
    "python -m ipykernel install --user --name safesort-py314 --display-name \"SafeSort Python 3.14\"\n",
    "jupyter lab\n",
    "```\n",
    "\n",
    "Select the **SafeSort Python 3.14** kernel. The diagnostic cell below must\n",
    "point into this `.venv` and the cloned `src/safesort` tree."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "diagnostic-23-09",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "import safesort\n",
    "\n",
    "print(sys.executable)\n",
    "print(safesort.__file__)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-09-03",
   "metadata": {},
   "source": [
    "## Цель\n",
    "\n",
    "Прогнать настоящую функцию `safesort.scanner.scan()` по временному каталогу с вложенными файлами и убедиться, что она находит именно то, что нужно, — и ничего лишнего."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-09-04",
   "metadata": {},
   "source": [
    "## Example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-23-09-05",
   "metadata": {},
   "outputs": [],
   "source": [
    "import tempfile\n",
    "from pathlib import Path\n",
    "\n",
    "from safesort.config import Config\n",
    "from safesort.scanner import scan\n",
    "\n",
    "tmpdir = tempfile.TemporaryDirectory()\n",
    "koren = Path(tmpdir.name)\n",
    "\n",
    "(koren / \"podkatalog\").mkdir()\n",
    "(koren / \"podkatalog\" / \"otchet.pdf\").write_text(\"...\", encoding=\"utf-8\")\n",
    "(koren / \"photo.jpg\").write_text(\"...\", encoding=\"utf-8\")\n",
    "(koren / \".git\").mkdir()\n",
    "(koren / \".git\" / \"config\").write_text(\"...\", encoding=\"utf-8\")\n",
    "\n",
    "fajly = scan(koren, Config())\n",
    "imena = {f.path.name for f in fajly}\n",
    "print(\"Найдено файлов:\", len(fajly))\n",
    "print(imena)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-09-06",
   "metadata": {},
   "source": [
    "## Проверка результата"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-23-09-07",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert imena == {\"otchet.pdf\", \"photo.jpg\"}\n",
    "assert all(f.path.name != \"config\" for f in fajly)  # .git исключён по умолчанию\n",
    "print(\"Верно: сканер нашёл вложенный файл и файл в корне, но не заглянул в .git.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-09-08",
   "metadata": {},
   "source": [
    "## Эксперимент — повторный запуск не находит уже отсортированные файлы"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-23-09-09",
   "metadata": {},
   "outputs": [],
   "source": [
    "(koren / \"Sorted\" / \"documents\").mkdir(parents=True)\n",
    "(koren / \"Sorted\" / \"documents\" / \"staryj.pdf\").write_text(\"...\", encoding=\"utf-8\")\n",
    "\n",
    "fajly_posle = scan(koren, Config())\n",
    "imena_posle = {f.path.name for f in fajly_posle}\n",
    "\n",
    "assert \"staryj.pdf\" not in imena_posle\n",
    "assert imena_posle == {\"otchet.pdf\", \"photo.jpg\"}\n",
    "print(\"Верно: каталог результата Sorted/ исключён из повторного сканирования.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-09-10",
   "metadata": {},
   "source": [
    "## Starter\n",
    "\n",
    "Заполните отмеченное место. Неизменённый starter не проходит tests."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "task-23-09",
   "metadata": {
    "tags": [
     "exercise",
     "starter"
    ]
   },
   "outputs": [],
   "source": [
    "def proverit_propusk_ssylki(root: Path):\n",
    "    # TODO: create photo.jpg and a symlink, call scan(), inspect names.\n",
    "    raise NotImplementedError\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-09-12",
   "metadata": {},
   "source": [
    "## Task\n",
    "\n",
    "Напишите `proverit_propusk_ssylki(root)`: создайте target и symlink, затем верните `True`, если scan пропустил ссылку. При отсутствии поддержки верните `None`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-09-13",
   "metadata": {},
   "source": [
    "## Tests\n",
    "\n",
    "Запустите после task cell: есть основной пример и хотя бы один крайний случай."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "tests-23-09",
   "metadata": {
    "tags": [
     "exercise-tests"
    ]
   },
   "outputs": [],
   "source": [
    "with tempfile.TemporaryDirectory() as tmp:\n",
    "    rezultat_ssylki = proverit_propusk_ssylki(Path(tmp))\n",
    "assert rezultat_ssylki in (True, None)\n",
    "\n",
    "# Edge case: an empty directory has no scan results.\n",
    "with tempfile.TemporaryDirectory() as tmp:\n",
    "    assert scan(Path(tmp), Config()) == []\n",
    "print(\"Tests passed\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-09-15",
   "metadata": {},
   "source": [
    "## Hint\n",
    "\n",
    "Перехватите `(OSError, NotImplementedError)` только вокруг `symlink_to()`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-09-16",
   "metadata": {},
   "source": [
    "## Solution\n",
    "\n",
    "<details><summary>Показать решение после собственной попытки</summary>\n",
    "\n",
    "```python\n",
    "def proverit_propusk_ssylki(root: Path):\n",
    "    target = root / \"photo.jpg\"\n",
    "    target.write_text(\"photo\", encoding=\"utf-8\")\n",
    "    link = root / \"ssylka_na_foto.jpg\"\n",
    "    try:\n",
    "        link.symlink_to(target)\n",
    "    except (OSError, NotImplementedError):\n",
    "        return None\n",
    "\n",
    "    names = {file.path.name for file in scan(root, Config())}\n",
    "    return \"photo.jpg\" in names and \"ssylka_na_foto.jpg\" not in names\n",
    "```\n",
    "\n",
    "</details>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Cartesian Python 3.14",
   "language": "python",
   "name": "cartesian-python314"
  },
  "language_info": {
   "name": "python",
   "version": "3.14.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
