Skip to content

Instantly share code, notes, and snippets.

@guid-empty
Last active April 9, 2024 08:10
Show Gist options
  • Save guid-empty/9d934f79d9cfed4beb6738b0f5193c30 to your computer and use it in GitHub Desktop.
Save guid-empty/9d934f79d9cfed4beb6738b0f5193c30 to your computer and use it in GitHub Desktop.
Template Processor
import 'package:models/src/models/participant_descriptor_model.dart';
import 'package:models/src/models/quiz_session_model.dart';
import 'package:models/src/models/quiz_session_result_item_model.dart';
import 'package:models/src/models/templates/context_variables.dart';
typedef LazyValueGetter = Object? Function();
class TemplatesProcessorService {
static const _variableExpression = r'(?<variable>\%\w*\%)';
static final _variableExpressionRegex = RegExp(_variableExpression);
String processTemplate({
required String template,
required QuizSessionModel session,
QuizSessionResultItemModel? perUserResults,
ParticipantDescriptorModel? participant,
Iterable<String>? questionAnswers,
}) {
final environment = <String, LazyValueGetter>{
ContextVariables.quizSessionId: () => session.id.value,
ContextVariables.quizId: () => session.currentQuiz.id.value,
ContextVariables.quizTitle: () => session.currentQuiz.title,
ContextVariables.participantFullName: () =>
participant?.name ?? 'Участник',
ContextVariables.participantName: () => participant?.name ?? 'Участник',
ContextVariables.userName: () => participant?.name ?? 'Участник',
ContextVariables.totalCorrectlyAnsweredQuestionsElapsedTime: () =>
_printDuration(
perUserResults?.totalCorrectlyAnsweredQuestionsElapsedTime,
),
ContextVariables.totalQuestionsCount: () =>
perUserResults?.totalQuestionsCount,
ContextVariables.totalAnsweredQuestionsCount: () =>
perUserResults?.totalAnsweredQuestionsCount,
ContextVariables.totalCorrectlyAnsweredQuestionsCount: () =>
perUserResults?.totalCorrectlyAnsweredQuestionsCount,
ContextVariables.totalIncorrectlyAnsweredQuestionsCount: () =>
perUserResults?.totalIncorrectlyAnsweredQuestionsCount,
ContextVariables.totalCorrectlyAnsweredQuestionsWeight: () =>
perUserResults?.totalCorrectlyAnsweredQuestionsWeight,
ContextVariables.totalSkippedQuestionsCount: () =>
perUserResults?.totalSkippedQuestionsCount,
ContextVariables.participantCurrentQuestionAnswers: () {
if (questionAnswers != null && questionAnswers.isNotEmpty) {
if (questionAnswers.length == 1) {
return questionAnswers.first;
} else {
return questionAnswers.join(', ');
}
}
return '(Нет данных)';
},
ContextVariables.quizLeaderPosition: () =>
(perUserResults?.isQuiz ?? false)
? perUserResults?.maybeMap(
quiz: (q) => q.quizLeaderPosition,
orElse: () => 0,
)
: 0,
ContextVariables.isQuizSuccessful: () => (perUserResults?.isQuiz ?? false)
? perUserResults?.maybeMap(
quiz: (q) => q.isQuizSuccessful,
orElse: () => false,
)
: false,
ContextVariables.isTestSuccessful: () => (perUserResults?.isTest ?? false)
? perUserResults?.maybeMap(
test: (q) => q.isTest,
orElse: () => false,
)
: false,
};
final updatedTemplateText = template.replaceAllMapped(
_variableExpressionRegex,
(match) {
final variableName = match[0].toString().replaceAll('%', '');
if (environment.containsKey(variableName)) {
return environment[variableName]?.call().toString() ?? '';
} else {
return match[0].toString();
}
},
);
return updatedTemplateText;
}
String _printDuration(Duration? duration) {
if (duration == null) {
return '';
}
final minutes = duration.inMinutes.remainder(60);
final seconds = duration.inSeconds.remainder(60);
if (duration.inHours > 0) {
return '${duration.inHours} часов и $minutes минут';
}
if (duration.inMinutes < 1) {
return '$seconds сек';
} else {
return '$minutes минут $seconds сек';
}
}
}
@guid-empty
Copy link
Author

guid-empty commented Apr 8, 2024

Использую сейчас следующий принцип обработки текстов.
Есть шаблоны, в тексте которых подставляю переменные из сессии/окружения пользователя.
Выглядеть шаблон может примерно так:

const String calculationModelYouAreTheLeaderOfQuizTemplate = '''
Поздравляю тебя, %${ContextVariables.participantName}% ${Emojis.handshake}.
Ты успешно прошел Квиз "%${ContextVariables.quizTitle}%" и занял 
${ContextVariables.quizLeaderPosition} место.
Твой результат  - 
    %${ContextVariables.totalCorrectlyAnsweredQuestionsCount}% успешных вопросов из 
    общего кол-ва %${ContextVariables.totalQuestionsCount}%.
И ты потратил на это %${ContextVariables.totalCorrectlyAnsweredQuestionsElapsedTime}%!.
''';

Первый алгоритм "в лоб", который был - я каждый раз при вызове метода processTemplate передавал необходимые сущности для построения мапки с переменными - environment. И потом по каждому ключу из этой мапки выполнял в цикле replace.

    var message = template;
    for (final entry in environment.entries) {
      message = message.replaceAll('%${entry.key}%', entry.value.toString());
    }

    return message;

Не трудно догадаться, что очень жирное вычисление.
Когда переменных было раз два, не страшно. Когда их перевалило за 20, на каждое сообщение выполнять такое процессинг стало просто постыдным )).

И тут неожиданном пригодились опять Regex.
Сейчас на сроке template.replaceAllMapped выполняю replace только той части строки, которая попадает под условие поиска переменных. Получив имя переменной я дальше вычисляю ее значение из environment опять же только по требованию, лениво, по ключу вызывая необходимый геттер с замыканием.

    final updatedTemplateText = template.replaceAllMapped(
      _variableExpressionRegex,
      (match) {
        final contextVariable = match[0].toString().replaceAll('%', '');
        return environment[contextVariable]?.call().toString() ?? '';
      },
    );

Оптимизации часто преждевременны. Но тут мне было реально стыдно )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment