Слияние 2 почти идентичных функций в Perl

У меня есть скрипт, который получает некоторые данные с веб-сайта. Данные поступают в формате JSON, и сайт предлагает возможность «свести» вывод JSON в один объект JSON или оставить его в виде нескольких объектов.

Сценарий имеет параметры, позволяющие преобразовывать данные JSON в YAML (независимо от того, сведены они или нет) или оставлять их в формате JSON.

Кроме того, скрипт раскрашивает значения в обоих форматах.

Чтобы выполнить раскраску, у меня в настоящее время есть 2 функции: одна для раскраски JSON и одна для раскраски YAML.

Сама раскраска достигается с помощью Term::ANSIColor путем поиска и замены текста либо в скаляре, либо в массиве, в зависимости от того, в каком формате вывода находятся данные.

Я хотел бы свести это к одной функции, чтобы уменьшить дублирование кода, но я не знаю, как это сделать.

Для ясности, для ясности основное внимание в этом вопросе уделяется тому, как сделать одну из функций раскрашивания многоразовой, чтобы она могла работать как с выводом YAML, так и с выводом JSON. Поскольку шаблоны поиска очень похожи, а шаблоны замены идентичны, мне кажется, что это должно быть очень легко сделать, но я не знаю, как это сделать.

use JSON;
use YAML::Tiny;

sub colorize_yaml
{
    my $OUTPUT                                                                               = shift;
    my $OPTIONS                                                                              = shift;

    if (ref $OUTPUT eq 'SCALAR')
    {
        foreach (${$OUTPUT})
        {

            # Hide this if debugging is disabled, else show it and color it
            if (!$OPTIONS->{debug})
            {
                s{(statusCode|success|dataExist|verumModelObjectName):\ [a-zA-Z0-9]+\n}
{}gxms;
            }
            else
            {
            s{(statusCode|success|dataExist|verumModelObjectName):}
{$OPTIONS->{color} ? BOLD YELLOW $1 . ':', BOLD GREEN : $1 . ':'}gxmse;
            }

            # Colorize 5 segment flat output
            s{([a-zA-Z0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9]+:\ )}
{$OPTIONS->{color} ? BOLD CYAN $1, BOLD YELLOW $2, BOLD MAGENTA $3, BOLD RED $4, RESET $5: $1 . $2 . $3 . $4 . $5}gxmse;

            # Colorize 4 segment flat output
            s{([a-zA-Z0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9]+:\ )}
{$OPTIONS->{color} ? BOLD CYAN $1, BOLD YELLOW $2, BOLD MAGENTA $3, RESET $4 : $1 . $2 . $3 . $4}gxmse;

            # Colorize 3 segment flat output
            s{([a-zA-Z0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9]+:\ )}
{$OPTIONS->{color} ? BOLD CYAN $1, BOLD YELLOW $2, RESET $3 : $1 . $2 . $3}gxmse;

            # Colorize 2 segment flat output
            s{([a-zA-Z0-9]+:)([a-zA-Z0-9]+:\ )}
{$OPTIONS->{color} ? BOLD CYAN $1, RESET $2 : $1 . $2}gxmse;

            # Colorize values in all output
            s{(:\ )}
{$OPTIONS->{color} ? $1 . BOLD GREEN : $1}gxmse;

            # Reset colors before newlines so that the next line starts with a clean color pattern.
            s{\n}
{$OPTIONS->{color} ? RESET "\n" : "\n"}gxmse;
        }
    }
    else
    {
        pretty_print_error("WARNING: Unable to colorize YAML output\n", $OPTIONS->{color});
        return;
    }

    return;
}

sub colorize_json
{
    my $OUTPUT                                                                               = shift;
    my $OPTIONS                                                                              = shift;

    if (ref $OUTPUT eq 'ARRAY')
    {
        foreach (@{$OUTPUT})
        {
            if ($OPTIONS->{debug})
            {
                s{(statusCode|success|dataExist|verumModelObjectName):}
{$OPTIONS->{color} ? BOLD YELLOW $1 . ':', BOLD GREEN : $1 . ':'}gxmse;
            }
            else
            {
                s{(statusCode|success|dataExist|verumModelObjectName):\ [a-zA-Z0-9]+\n}
{}gxms;
            }

            # Colorize 5 segment flat output
            s{^([a-zA-Z0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9]+:\ .*$)}
{$OPTIONS->{color} ? BOLD CYAN $1, BOLD YELLOW $2, BOLD MAGENTA $3, BOLD RED, $4, RESET $5: $1 . $2 . $3 . $4 . $5}gxmse;

            # Colorize 4 segment flat output
            s{^([a-zA-Z0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9]+:\ )}
{$OPTIONS->{color} ? BOLD CYAN $1, BOLD YELLOW $2, BOLD MAGENTA $3, RESET $4 : $1 . $2 . $3 . $4}gxmse;

            # Colorize 3 segment flat output
            s{^([a-zA-Z0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9]+:\ )}
{$OPTIONS->{color} ? BOLD CYAN $1, BOLD YELLOW $2, RESET $3 : $1 . $2 . $3}gxmse;

            # Colorize 2 segment flat output
            s{^([a-zA-Z0-9]+:)([a-zA-Z0-9]+:\ )}
{$OPTIONS->{color} ? BOLD CYAN $1, RESET $2 : $1 . $2}gxmse;

            # Colorize values in all output
            s{(:\ )}
{$OPTIONS->{color} ? $1 . BOLD GREEN : $1}gxmse;

            # Reset colors before newlines so that the next line starts with a clean color pattern.
            s{$}
{$OPTIONS->{color} ? RESET '' : ''}gxmse;
        }
    }
    else
    {
        pretty_print_error("WARNING: Unable to colorize JSON output.\n", $OPTIONS->{color});
        return;
    }

    return;
}

JSON преобразован в YAML

---
message: Success
ObjectList:
  -
    assetName: xxxxxxxx
    backupAsset:
      -
        backupFlag: xxxxxxxx
        fullyCertified: xxxxxxxx

Плоский JSON преобразован в YAML

---
message: Success
verumObjectList:
  -
    assetName: xxxxxxxx
    backupAsset:backupFlag: xxxxxxxx
    backupAsset:fullyCertified: xxxxxxxx

JSON (данные в формате JSON удаляются скриптом, чтобы сделать их простым текстом)

assetName: xxxxxxxx
backupFlag: xxxxxxxx
fullyCertified: xxxxxxxx
message: Success

Плоский JSON (данные в формате JSON удаляются скриптом, чтобы сделать их простым текстом)

assetName: xxxxxxxx
backupAsset:backupFlag: xxxxxxxx
backupAsset:fullyCertified: xxxxxxxx
message: Success

Правильный ответ присуждается @zdim, хотя мне пришлось немного изменить код.

Я отправляю свой обновленный код ниже.

use JSON;
use YAML::Tiny;

sub colorize_output
{
    my $OUTPUT   = shift;
    my $OPTIONS  = shift;

    my $RE_START = $EMPTY;
    my $RE_END   = q{\ };

    if (ref $OUTPUT eq $EMPTY)
    {   
        pretty_print_error("WARNING: Unable to colorize output.\n", 
            $OPTIONS->{color});
        return;
    }   
    elsif (ref $OUTPUT eq 'ARRAY')
    {   
        $RE_START = q{^};
        $RE_END   = q{\ .*};
    }   

    my $ANCHOR    = q{[a-zA-Z0-9]+:};
    my $PATTERN   = qq{($ANCHOR)};

    Readonly my $SEGMENT_LIMIT => 4;

    my $VERUM_RE = qr{(statusCode|success|dataExist|verumModelObjectName):}xms;

    my ($SEGMENT_2PART_RE, $SEGMENT_3PART_RE, $SEGMENT_4PART_RE, $SEGMENT_5PART_RE)
        = map { 
            qr{$RE_START}xms . ($PATTERN x $ARG) . qr{($ANCHOR$RE_END)}xms 
        } 1..$SEGMENT_LIMIT;

    foreach ((ref $OUTPUT eq 'SCALAR')?${$OUTPUT}:@{$OUTPUT})
    {   

        # Hide this if debugging is disabled, else show it and color it
        if (!$OPTIONS->{debug})
        {   
            s{$VERUM_RE\ [a-zA-Z0-9]+}{}gxms;
        }   
        else
        {   
            s{$VERUM_RE}
             {$OPTIONS->{color} ? BOLD YELLOW $1 . ':', BOLD GREEN : $1 . ':'}gxmse;
        }   

        # Colorize sections in flat output
        if ($OPTIONS->{color})
        {   
            s{$SEGMENT_5PART_RE}
             {BOLD CYAN $1, BOLD YELLOW $2, BOLD MAGENTA $3, BOLD RED $4, RESET $5}gxmse;
            s{$SEGMENT_4PART_RE}
             {BOLD CYAN $1, BOLD YELLOW $2, BOLD MAGENTA $3, RESET $4}gxmse;
            s{$SEGMENT_3PART_RE}
             {BOLD CYAN $1, BOLD YELLOW $2, RESET $3}gxmse;
            s{$SEGMENT_2PART_RE}
             {BOLD CYAN $1, RESET $2}gxmse;

            # Colorize values in all output
            s{(:\ )}{$1 . BOLD GREEN}gxmse;

            # Reset colors before newlines or next entry in the list so that
            # the next line starts with a clean color pattern.
            s{(\n|$)}{RESET $1}gxmse;
        }   
    }   

    return;
}   

person Speeddymon    schedule 06.12.2017    source источник
comment
Нам нужно иметь примеры данных для обоих форматов. Было бы лучше, если бы вы действительно написали модульный тест и включили его. В качестве примера того, как писать модульные тесты для раскраски, не стесняйтесь заимствовать из мои тесты модуля Dancer2::Logger::Console::Colored.   -  person simbabque    schedule 06.12.2017
comment
Если задача состоит в том, чтобы раскрасить YAML, вы также можете взглянуть на YAML::PP (отказ от ответственности : от себя). Он может выделять YAML, а поскольку YAML является (почти) надмножеством JSON, он также может раскрашивать JSON. Но выделение в настоящее время отличается от того, что вы хотите, я думаю.   -  person tinita    schedule 06.12.2017
comment
@tinita К сожалению, машина, на которой выполняется код и загружаются данные, не имеет внешнего веб-доступа, а имеет только внутренний доступ и доступ к определенным репозиториям, созданным нашими инженерами. Из-за этого я использую YAML::Tiny и не могу установить YAML::PP из CPAN или откуда-то еще. :-(   -  person Speeddymon    schedule 06.12.2017
comment
@simbabque Я рассмотрю модульные тесты. Я опубликую образцы данных во всех 4 форматах (json, сглаженный json, json, преобразованный в yaml, и сглаженный json, преобразованный в yaml) — скоро   -  person Speeddymon    schedule 06.12.2017
comment
Ok. Большой. У меня осталось около полутора часов езды на поезде. :)   -  person simbabque    schedule 06.12.2017
comment
@simbabque похоже, что ваш тест против модуля. Я не пишу модуль. Можно ли еще сделать? Какие изменения необходимы для модульного тестирования скрипта, который не является модулем?   -  person Speeddymon    schedule 06.12.2017
comment
Ну, для этого примера вы можете просто иметь файл, который включает Test::More (и др.) и функции, которые вы хотите протестировать. А потом запустить это. Если вы хотите сохранить эти тесты, хорошим подходом будет размещение do "script.pl"; в верхней части файла модульного теста. Это даст вам функции. Но он также будет запускать код, поэтому архитектура вашего скрипта имеет значение. Пока я просто предложил тест, потому что показать нам ожидаемый результат с терминальными цветами будет ... интересно. ;)   -  person simbabque    schedule 06.12.2017
comment
Ах да, цвета имеют значение, но для примера не так уж и много. Я могу работать с самими цветами, я просто хочу максимально дедуплицировать. Как видно из кода, большая часть кода дублируется, единственные реальные различия заключаются в проверке ссылки ARRAY в colorize_json и проверке ссылки SCALAR в colorize_yaml, а также в использовании ^ и $ в colorize_json и \n в colorize_yaml.   -  person Speeddymon    schedule 06.12.2017
comment
Зачем нужна разница в схеме?   -  person simbabque    schedule 06.12.2017
comment
Вам также нужно показать нам, как вы вызываете функцию. Либо добавьте код синтаксического анализа, либо выгрузите структуры данных после их импорта. Меня не особенно волнует, откуда эта проблема.   -  person simbabque    schedule 06.12.2017
comment
Разница в шаблоне необходима, потому что при преобразовании из JSON в YAML данные выгружаются в скаляр с помощью метода Yaml::Tiny Dump, который заставляет присутствовать символы новой строки. В необработанном формате JSON я сначала преобразовал данные в массив, так как было проще удалить теги JSON из каждой строки вывода по отдельности, чем делать это как одну гигантскую строку. Поскольку мы ничего не удаляем из преобразованного вывода YAML, было проще оставить его как одну гигантскую строку.   -  person Speeddymon    schedule 06.12.2017
comment
Итак, вы хотите выделить его, а затем вывести JSON или YAML на экран? Теперь я понимаю. Является ли выделение значений перед преобразованием опцией?   -  person simbabque    schedule 06.12.2017
comment
Да, но потом YAML::Tiny задыхается от выделения символов. :-( YAML::PP не задохнется, но, к сожалению, его нельзя установить на этот хост, не прыгая через некоторые огромные корпоративные обручи.   -  person Speeddymon    schedule 06.12.2017
comment
Кажется, вы написали одноцелевой парсер JSON/YAML. Я думаю, что я бы преобразовал оба в данные Perl, а также отобразил и раскрасил их вместо этого.   -  person Borodin    schedule 06.12.2017
comment
Одного я не понимаю... код под циклом foreach в этих функциях выглядит одинаково. Это правильно? Вы только спрашиваете, как заставить его обрабатывать либо scalarref, либо arrayref?   -  person zdim    schedule 06.12.2017
comment
Предложения о том, как это сделать, очень приветствуются. Единственная реальная потребность в use JSON и use YAML::Tiny — это преобразование данных JSON с веб-сайта в формат YAML для отображения данных на экране. Если бы я мог сделать это с данными Perl и при этом получить вывод либо в виде обычного текста, либо в формате YAML, это было бы намного лучше.   -  person Speeddymon    schedule 06.12.2017
comment
@zdim почти идентичны. Часть соответствия шаблону строк s/// немного отличается.   -  person Speeddymon    schedule 06.12.2017
comment
хорошо, теперь я вижу... это ^... это все?   -  person zdim    schedule 06.12.2017
comment
Разбил несколько очень длинных строк — трудно следовать коду, когда приходится прокручивать далеко вправо. Во что бы то ни стало, пожалуйста, откатитесь назад, если вам это не нравится (или, конечно, подправьте, если некоторые детали вас беспокоят).   -  person zdim    schedule 09.12.2017


Ответы (1)


Это отвечает на вопрос о том, как реорганизовать эти функции без какого-либо более широкого контекста.

Единственное отличие заключается во входных данных: это либо скалярная ссылка, либо массивная ссылка.

Два других различия в регулярном выражении более важны: шаблоны arrayref привязаны, и их последний буквенно-цифровой шаблон заканчивается на \ .*$, в то время как шаблоны scalarref не привязаны, и их последнее совпадение заканчивается экранированным пробелом.

Наконец, если $OPTIONS->{color} ложно, то во всех случаях весь шаблон заменяется сам собой; поэтому переменная не меняется. Тогда условие должно быть вытащено.

sub colorize_yaml_json {
    my ($OUTPUT, $OPTIONS) = @_;

    my $anchor = '';
    my $last   = qr{\ };
    my @iter_refs;

    if    (ref $OUTPUT eq 'SCALAR') { @iter_refs = $$OUTPUT }
    elsif (ref $OUTPUT eq 'ARRAY')  { 
        @iter_refs = @$OUTPUT;
        $anchor = qr{^};
        $last   = qr{\ .*$};
    }
    else {
        pretty_print_error(...);
        return;
    }

    my $anc  = qr{[a-zA-Z0-9]+:};  # alphanumeric with colon
    my $patt = qr{($anc)};

    my ($seg2_re, $seg3_re, $seg4_re, $seg5_re) = map { 
        qr/$anchor/ . ($patt x $_) . qr/($anc$last)/ 
    } 1..4;

    foreach (@iter_refs) {
        if ($OPTIONS->{debug}) {
            ...
        }        
        if ($OPTIONS->{color}) {
            s{$seg5_re}{BOLD CYAN $1, ... }gxmse;
            ...
        }
    }
    return 1;
}

map собирает весь шаблон для четырех случаев, складывая буквенно-цифровой (с :) шаблон $patt необходимые 25 раз, используя x N для 1..4, а затем добавляя последний шаблон.

Неприятная сложность заключается в том, что каждый из базовых шаблонов $anc должен быть захвачен.

Я мог проверить это только на своих макетных данных, поэтому, пожалуйста, проверьте внимательно (как всегда!).

Существует также другой вопрос, как лучше всего справиться со всем сценарием, но это был не вопрос, и поэтому недостаточно информации, чтобы справиться с ним без особых догадок.

person zdim    schedule 06.12.2017
comment
@Speeddymon Исходный код работал не так, как в вашем вопросе, исправлено. - person zdim; 06.12.2017
comment
Только что вернулся с совещания, попробую. Спасибо! - person Speeddymon; 07.12.2017
comment
Немного поэкспериментировав с этим, оказалось, что функция map правильно компилирует переменные, но не дает желаемого результата. Вместо объявления 4 переменных $seg?_re с map мне пришлось объявить их, как показано ниже: my $seg2_re = qr{$ANCHOR${patt}($anc$LAST)}xms; my $seg3_re = qr{$ANCHOR${patt}${patt}($anc$LAST)}xms; my $seg4_re = qr{$ANCHOR${patt}${patt}${patt}($anc$LAST)}xms; my $seg5_re = qr{$ANCHOR${patt}${patt}${patt}${patt}($anc$LAST)}xms; - person Speeddymon; 07.12.2017
comment
@Speeddymon Хорошо, спасибо за отзыв! В моих ограниченных тестах сработало, видимо, я зашел слишком далеко. Будет исправлено или иным образом обновлено (напишите их по одному, если map нельзя использовать). (Нужно бежать прямо сейчас) - person zdim; 07.12.2017
comment
Я позаботился, добавил обновленный код в свой OP и наградил вас ответом. Спасибо за помощь. На самом деле нужно было просто понять, что вы можете использовать имена переменных в разделе соответствия s/// - person Speeddymon; 07.12.2017
comment
@Speeddymon Спасибо за такой хороший отзыв и за указание авторства. Проблема заключалась в том, что $patt{2} - когда $patt имеет захватывающую скобку, она не работает должным образом. Я изменил это на $patt x 2map, поэтому 2 на самом деле $_), и теперь это работает в моих более обширных (все еще макетных) тестах. Другая проблема заключалась бы в том, что у меня был код, который сначала заменяет 2 экземпляра, затем 3... это не сработает; исправлено. Наконец, добавьте нужные флаги в s{}{}FLAGS. - person zdim; 07.12.2017
comment
@Speeddymon Еще одним упущением было $OUTPUT вместо $$OUTPUT (круглые скобки не нужны); исправлено - person zdim; 07.12.2017
comment
@Speeddymon Добавил комментарий в конце, немного подчистил - person zdim; 07.12.2017
comment
Спасибо за обновления. Мне не приходило в голову дробить паттерн в map, чтобы получить желаемый результат. Также спасибо за отзыв о замене. Первоначально я сделал это так, потому что perlcritic жаловался на то, что функция слишком сложна, но с некоторыми другими изменениями, которые я сделал, это уменьшило сложность настолько, что я смог обернуть ее в if, как вы предложили. что определенно легче читать! - person Speeddymon; 08.12.2017
comment
Еще один вопрос. Я заметил, что в вашем коде вы везде использовали qr//, а не только q// и qq//, а затем наличие qr// на самой карте было единственным qr//-- Есть ли для этого причина? Наличие только двух qr// на карте, а остальные q// и qq//, похоже, отлично работают для меня. - person Speeddymon; 08.12.2017
comment
@Speeddymon Способ map более компактен и универсален, но их изложение в том виде, в котором они есть сейчас, легче просмотреть. Затем, если все в порядке, я перефразирую последнюю часть, чтобы удалить условные выражения (Похоже, что..., если это действительно так...). - person zdim; 08.12.2017
comment
@Speeddymon qr в целом лучше подходит для регулярных выражений, поскольку затем можно использовать полное регулярное выражение внутри. Однако для большинства распространенных шаблонов регулярных выражений также работает формирование строки с q(). Я был бы осторожен с двойными кавычками qq(), так как интерполяция может привести к неожиданностям (и регулярное выражение все равно сделает это сознательно, как только переменная будет оценена для шаблона). Я не знаю, почему я был непоследователен; Я думаю, что q() (что было в map?) осталось от какого-то тестирования. Теперь они все qr. - person zdim; 08.12.2017
comment
@Speeddymon Еще один комментарий о Perl Critic. Это полезный ресурс, без вопросов. Однако это не окончательное средство проверки кода — и оно не предназначено для этого. Это отличный пример. Гораздо лучше избавиться от этого состояния. Если Критик так не думает... Я рассмотрю функцию, и если я ничего не хочу делать, я оставлю ее; критик не может сказать мне, как писать код, и если вы посмотрите Perl Best Practices (книга, на которой он тесно основан), он и не собирается этого делать. Тем не менее, я запускаю его время от времени, как проверку и пищу для размышлений. - person zdim; 08.12.2017