На работе сейчас занимаюсь написанием инструмента сборки линуксовых пакетов, который должен заменить огромное количество однотипных Bash-скриптов и манифестов пакетов. Инструмент готов, теперь нужно как-то переписать исходные скрипты и манифесты в новый конфиг, вручную это делать я конечно же не собираюсь.
В числе прочего, нужно распарсить RPM-манифест, чтобы выдрать оттуда кастомные скрипты настройки пакета.
У RPM-манифестов есть два форматов, в которых они хранят свою мета-информацию: атрибуты и макросы
Атрибуты
Здесь всё просто: В одной строке название атрибута и через двоеточие его значение:
Summary: Some text
^Summary:\s+(.*)$
Так можно достать Some text
Макросы
А вот это уже довольно хитровыдуманная штука. Здесь есть заголовок, начинающийся с процента, а сам макрос пишется со следующей строки, причём строк может быть множество.
%description
some
multiline
text
%another-macro
...
С помощью подруги вот к какому решению я пришёл:
^%description((?:\r?\n.+){{1,}})
Таким способом можно выдрать \nsome\nmultiline\ntext
, а дальше уже вопрос постобработки (заменить переносы строк на пробелы и почистить лишнее)
Да, этот вариант не всегда будет корректно работать (например, для случаев, когда нужен макрос, в котором объявлен какой-нибудь скрипт с пустыми строками в нём, оставленными для читаемости), но для базовых случаев он подходит.
Улучшаем регулярку для макросов
Более корректной постановкой вопроса будет "брать всё после заголовка до следующего макроса", поэтому построим более сложную регулярку:
^%description(?:\r?\n(?!%).*)*
Такое выражение точно будет работать корректно и с макросами, которые содержат пустые строки
Загнать всё в код
Эти регулярки мне нужны в C#-приложении, вот такой класс я для них написал:
public static class RpmParser
{
public static string ExtractProperty(string manifest, string name)
{
var match = Regex.Match(manifest, @$"^{name}:\s+(.*)$", RegexOptions.Compiled | RegexOptions.Multiline);
if (!match.Success)
{
return string.Empty;
}
return match.Groups[1].Value;
}
public static string ExtractMacro(string manifest, string name)
{
var match = Regex.Match(manifest, $@"^%{name}((?:\r?\n(?!%).*)*)",
RegexOptions.Compiled | RegexOptions.Multiline);
if (!match.Success)
{
return string.Empty;
}
return match
.Groups[1].Value
.Replace("\n", " ")
.Trim(' ');
}
}