Файлова система, дешево і швидко

Розробникам часто доводиться мати справу з файлами, що представляють з себе
деревоподібну структуру: XML, JSON, YAML, всякого роду мови розмітки на зразок
Markdown або Org-mode. Полегшуючи в загальному і цілому наше життя, такі файли мають
схильність до безконтрольного зростання, в якийсь момент з рішення перетворюючись в
проблему.
Стандартне рішення цієї проблеми — розбиття на менші файли. Це, звичайно,
працює, але не завжди зручно.
Але існує й альтернатива, про яку нижче.
org-mode та його розмітка.
Мабуть, варто спочатку викласти мою проблему. Я використовую Емакс і — як багато
користувачі Емакса — для написання майже всіх моїх документів, заміток,
робочого щоденника і списків завдань використовую мову розмітки org-mode. Виглядає документ в цій розмітці приблизно наступним чином:
... простий приклад файлу з репозиторію ...
> cat tests/simple.org
document section
* headline 1
headline section 1
** inner headline 1
some inner section 1
some inner section 1-2
** inner headline 2
inner section 2
** inner headline 3
*** inner inner headline 1
* headline 2
section text 2

Один мій документ розрісся до декількох сотень підзаголовків різної глибини
вкладеності, і за всім цим заголовкам я регулярно проходжуюся скриптами в
пошуках різної інформації. Розбирати документ на декілька файлів не хотілося,
т. к. синхронізувати між машинами або яким-небудь скриптом обробляти
файл все ж легше. Але й далі так жити було рішуче неможливо.
І тоді мені в голову прийшло, що було б здорово ходити по моєму файлу як за
директоріях, за допомогою, скажімо, стандартних Юниксах
cd headline1
або
cd ..
,
ls -l
та
cat section
.
Іншими словами, мені захотілося вміти представляти дерево заголовків і текстових
секцій у вигляді звичайного дерева директорій і файлів. В термінах тих же
Юниксов це бажання звучить наступним чином: змонтувати якусь
спеціалізовану файлову систему.
Звичайно, писати повноцінну файлову систему для Лінукса — справа довга,
невдячна і вже точно не варто воно того в такого роду рідкісних випадках.
FUSE
Втім, у наші дні вже ніхто так і не робить, тобто з тих пір, як років
десять тому в Лінукс був включений модуль FUSE, що дозволяє робити файлові
системи у вигляді звичайного користувацького процесу, на який з ядра
маршрутизуються всі пов'язані з змонтованій файловій системою /системні
виклики.
З допомогою FUSE було написано безліч самих різних файлових систем, від
іграшкових ФС, вмонтовують, наприклад, статті з Вікіпедії, до цілком серйозних
частин сучасних Линуксов зразок того ж Gnome. Таким чином, FUSE став
обов'язковим елементом популярних дистрибутивів.
Ще приємніше роботу з FUSE робить той факт, що в наші дні доступні зовсім вже
тривіальні у використанні обгортки на високорівневих мовах начебто Python,
Ruby, Java і багатьох інших, тобто власну файлову систему можна зробити
буквально за два-три години.
fusepy
Конкретно на Пітоні обгорток навколо
libfuse
(клієнтської частини FUSE) навіть
кілька, але найбільше мені сподобався проект fusepy: код проекту дуже
простий і зрозумілий, крім прикладів на Гітхабі та вихідного коду мені так нічого
і не знадобилося.
Файлова система на базі
fusepy
зводиться до переопределению методів класу
fuse.Operations
, кожен з яких відповідає якому-небудь системного
викликом.
Для непереопределенных системних викликів є небудь розумне поведінка за
промовчанням, або стандартна помилка.
Orgfuse
Власне, конкретний формат файлу, який хочеться представити у вигляді дерева
директорій і файлів, не так важливий. У випадку з розміткою
org-mode
мені не
сподобався жоден з доступних парсерів для Пітона, і я просто написав
власний. Парсер проходить за файлом, створюючи дерево, що відбиває
структуру документа.
Дерево розбору (parse tree) файлу розмітки далі перетворюється в інше
дерево
, відображає файли і директорії, які буде бачити користувач
файлової системи.
Щоб працювати з останнім деревом було достатньо реалізувати чотири
системних викликів (
open
,
read
,
readdir
,
getattr
), кожен з яких займав
буквально кілька рядків коду на Пітоні:
class FuseOperations(Operations):

def __init__(self, tree):
self.tree = tree
self.fd = 0

def open(self, path, flags):
self.fd += 1
return self.fd

def read(self, path, size, offset, fh):
node = self.tree.find_path(path)
if node is None:
raise FuseOSError(EIO)
return node.content[offset:offset + size]

def readdir(self, path, fh):
node = self.tree.find_path(path)
if node is None:
raise FuseOSError(EROFS)
return ['.', '..'] + [child for child in node.children]

def getattr(self, path, fh=None):
node = self.tree.find_path(path)
if node is None:
raise FuseOSError(ENOENT)
return node.get_attrs()

Підсумковий скрипт працює приблизно наступним чином:
... монтуємо файл як файлову систему ...
> mkdir mount
> python orgfuse.py tests/simple.org mount/
... відкриваємо інший термінал і насолоджуємося ...
> tree mount
mount/
├── headline 1
│ ├── inner headline 1
│ │ └── section
│ ├── inner headline 2
│ │ └── section
│ ├── inner headline 3
│ │ └── inner inner headline 1
│ └── section
├── headline 2
│ └── section
└── section

6 directories, 5 files

Все це диво займає близько двох сотень рядків або 3-4 години моєї ледачою
вечірньої роботи, з моєї маленької завданням справляється чудово.
Інструкції по установці і код, як водиться, можна знайти на Github.
Якщо кому цікаво преращение прототипу у щось легкотравне, з
можливістю редагування файлів і підтримкою значно більшої кількості форматів — буду радий поспілкуватися.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.