- PVSM.RU - https://www.pvsm.ru -
Разработчикам часто приходится иметь дело с файлами, представляющими из себя
древовидную структуру: XML, JSON, YAML, всякого рода языки разметки вроде
Markdown или Org-mode. Облегчая в общем и целом нашу жизнь, такие файлы имеют
склонность к бесконтрольному росту, в какой-то момент из решения превращаясь в
проблему.
Стандартное решение этой проблемы — разбиение на меньшие файлы. Это, конечно,
работает, но не всегда удобно.
Но существует и альтернатива, о которой — ниже.
Пожалуй, стоит сначала изложить мою проблему. Я использую Емакс и — как многие
пользователи Емакса — для написания почти всех моих документов, заметок,
рабочего дневника и списков задач использую язык разметки org-mode [1]. Выглядит документ в этой разметке примерно следующим образом:
... простой пример файла из репозитория ...
> 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 [2], позволяющий делать файловые
системы в виде обыкновенного пользовательского процесса, на который из ядра
маршрутизируются все связанные со смонтированной файловой системой /системные
вызовы.
С помощью FUSE было написано множество самых разных файловых систем, от
игрушечных ФС, монтирующих, например, статьи с Википедии, до вполне серьезных
частей современных Линуксов вроде того же Gnome. Таким образом, FUSE стал
обязательным элементом популярных дистрибутивов.
Еще приятней работу с FUSE делает тот факт, что в наши дни доступны совсем уж
тривиальные в использовании обертки на высокоуровневых языках вроде Python,
Ruby, Java и многих других, т.е. собственную файловую систему можно сделать
буквально за два-три часа.
Конкретно на Питоне оберток вокруг libfuse
(клиентской части FUSE) даже
несколько, но больше всего мне понравился проект fusepy [3]: код проекта очень
простой и понятный, кроме примеров на Гитхабе и исходного кода мне так ничего
и не понадобилось.
Файловая система на базе fusepy
сводится к переопределению методов класса
fuse.Operations
, каждый из которых соответствует какому-либо системному
вызову.
Для непереопределенных системных вызовов есть либо разумное поведение по
умолчанию, либо стандартная ошибка.
Собственно, конкретный формат файла, который хочется представить в виде дерева
директорий и файлов, не так важен. В случае с разметкой org-mode
мне не
понравился ни один из доступных парсеров для Питона, и я просто написал
собственный [4]. Парсер проходит по указанному файлу, создавая дерево, отражающее
структуру документа.
Дерево разбора (parse tree) файла разметки дальше преобразуется в другое
дерево, отражающее файлы и директории, которые будет видеть пользователь
файловой системы.
Чтобы работать с последним деревом было достаточно реализовать четыре
системных вызовов (open
, read
, readdir
, getattr
), каждый из которых занимал
буквально несколько строк кода [5] на Питоне:
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 [6].
Если кому интересно преращение прототипа во что-то удобоваримое, с
возможностью редактирования файлов и поддержкой большего количества форматов — буду рад пообщаться.
Автор: VlK
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/211552
Ссылки в тексте:
[1] org-mode: https://ru.wikipedia.org/wiki/Org-mode
[2] FUSE: https://ru.wikipedia.org/wiki/FUSE_(%25D0%25BC%25D0%25BE%25D0%25B4%25D1%2583%25D0%25BB%25D1%258C_%25D1%258F%25D0%25B4%25D1%2580%25D0%25B0)
[3] fusepy: https://github.com/terencehonles/fusepy
[4] собственный: https://github.com/vkazanov/toy-orgfuse/blob/master/orgfuse.py#L14
[5] несколько строк кода: https://github.com/vkazanov/toy-orgfuse/blob/master/orgfuse.py#L140
[6] Github: https://github.com/vkazanov/toy-orgfuse/
[7] Источник: https://habrahabr.ru/post/315654/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.