2 минуты

На работе сейчас занимаюсь написанием инструмента сборки линуксовых пакетов, который должен заменить огромное количество однотипных Bash-скриптов и манифестов пакетов. Инструмент готов, теперь нужно как-то переписать исходные скрипты и манифесты в новый конфиг, вручную это делать я конечно же не собираюсь.

meme

В числе прочего, нужно распарсить 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(' ');
    }
}