128 lines
6.2 KiB
Dart
128 lines
6.2 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
||
|
||
void main() {
|
||
test('Markdown parser extracts all 5 blocks without loss or substring collisions', () {
|
||
const input = '''```markdown
|
||
## 1. ЯДРО / ФОРМУЛА ЗАКОНА:
|
||
(1-2 чеканных предложения)
|
||
Огонь — это первородная сингулярность трансформации материи в чистую радиацию духа.
|
||
|
||
## 2. ОРАКУЛ (МОНОЛИТНОЕ ЭССЕ):
|
||
(5-8 абзацев)
|
||
Праиндоевропейский корень *egni- фиксирует не субстанцию, а событие горения.
|
||
В шумерском мифе бог Гибил нисходит в тигель трансформации.
|
||
В алхимии огонь выступает универсальным растворителем формы ради освобождения скрытой субстанции.
|
||
|
||
## 3. ТЕНЕВАЯ ЛОВУШКА И ИСКАЖЕНИЕ:
|
||
(Теневая ловушка принципа)
|
||
Теневая ловушка принципа — испепеление контекста, мания тотального контроля через разрушение.
|
||
|
||
## 4. ЭКЗИСТЕНЦИАЛЬНЫЙ ВОПРОС:
|
||
(Острый коан)
|
||
Что именно в твоей структуре ты отказываешься отдать пламени обновления?
|
||
|
||
## 5. РИТУАЛЬНЫЙ ФИЗИЧЕСКИЙ ЖЕСТ:
|
||
(Конкретное микродействие)
|
||
Взять сухую щепку, разломить её надвое и бросить в открытый огонь, не оборачиваясь.
|
||
|
||
Метрика: 4120 символов
|
||
```''';
|
||
|
||
Map<String, String> parseRevisionBlocks(String text) {
|
||
final blocks = <String, String>{};
|
||
if (text.trim().isEmpty) return blocks;
|
||
|
||
var src = text.trim();
|
||
if (src.startsWith('```')) {
|
||
src = src.replaceFirst(RegExp(r'^```[a-zA-Z]*\n?'), '');
|
||
if (src.endsWith('```')) {
|
||
src = src.substring(0, src.length - 3).trim();
|
||
}
|
||
}
|
||
|
||
final lines = src.replaceAll('\r\n', '\n').split('\n');
|
||
|
||
String? detectSlot(String line) {
|
||
final stripped = line.trim();
|
||
if (stripped.isEmpty || stripped.length > 95) return null;
|
||
|
||
final isHeaderFormat = stripped.startsWith('#') ||
|
||
stripped.startsWith('*') ||
|
||
stripped.startsWith('[') ||
|
||
RegExp(r'^(?:\d+[\.\)]|[IVX]+[\.\)])').hasMatch(stripped) ||
|
||
(stripped.length < 70 && (stripped.endsWith(':') || stripped == stripped.toUpperCase())) ||
|
||
RegExp(r'\b(?:ЯДРО|ОРАКУЛ|ТЕНЬ|ВОПРОС|ЖЕСТ|КОАН)\b', caseSensitive: false).hasMatch(stripped);
|
||
|
||
if (!isHeaderFormat) return null;
|
||
|
||
if (RegExp(r'(?:^|[^\wа-яА-ЯёЁ])(?:ЯДР[ОАЕУ]|ФОРМУЛ[А-Яа-я]*|ОНТОЛОГИ[А-Яа-я]*)(?:[^\wа-яА-ЯёЁ]|$)', caseSensitive: false).hasMatch(stripped)) {
|
||
return 'ЯДРО';
|
||
}
|
||
if (RegExp(r'(?:^|[^\wа-яА-ЯёЁ])(?:ВОПРОС[А-Яа-я]*|КОАН[А-Яа-я]*|РЕФЛЕКСИ[А-Яа-я]*)(?:[^\wа-яА-ЯёЁ]|$)', caseSensitive: false).hasMatch(stripped)) {
|
||
return 'ВОПРОС';
|
||
}
|
||
if (RegExp(r'(?:^|[^\wа-яА-ЯёЁ])(?:ТЕН[ЬЯЕИЮ]|ТЕНЕВ[А-Яа-я]*|ЛОВУШК[А-Яа-я]*|ИСКАЖЕН[А-Яа-я]*|СЛЕП[А-Яа-я]*\s+ЗОН[А-Яа-я]*)(?:[^\wа-яА-ЯёЁ]|$)', caseSensitive: false).hasMatch(stripped)) {
|
||
return 'ТЕНЬ';
|
||
}
|
||
if (RegExp(r'(?:^|[^\wа-яА-ЯёЁ])(?:ЖЕСТ[А-Яа-я]*|МИКРОДЕЙСТВИ[А-Яа-я]*|ДЕЙСТВИ[ЕЯ]|ПРАКТИК[А-Яа-я]*)(?:[^\wа-яА-ЯёЁ]|$)', caseSensitive: false).hasMatch(stripped)) {
|
||
return 'ЖЕСТ';
|
||
}
|
||
if (RegExp(r'(?:^|[^\wа-яА-ЯёЁ])(?:ОРАКУЛ[А-Яа-я]*|СТАТЬ[ЯЕИЮ]|ЭССЕ|ТОЛКОВАНИ[А-Яа-я]*|ОБОГАЩЕНИ[А-Яа-я]*)(?:[^\wа-яА-ЯёЁ]|$)', caseSensitive: false).hasMatch(stripped)) {
|
||
return 'ОРАКУЛ';
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
final headers = <({int lineIdx, String slot})>[];
|
||
for (int i = 0; i < lines.length; i++) {
|
||
final slot = detectSlot(lines[i]);
|
||
if (slot != null) {
|
||
headers.add((lineIdx: i, slot: slot));
|
||
}
|
||
}
|
||
|
||
for (int i = 0; i < headers.length; i++) {
|
||
final startLineIdx = headers[i].lineIdx + 1;
|
||
final slot = headers[i].slot;
|
||
final endLineIdx = (i + 1 < headers.length) ? headers[i + 1].lineIdx : lines.length;
|
||
|
||
var content = lines.sublist(startLineIdx, endLineIdx).join('\n').trim();
|
||
|
||
content = content.replaceFirst(
|
||
RegExp(r'^\s*\([^)]*(?:предложен|абзац|коан|действи|формул|ловушк|стать)[^)]*\)\s*\n?', caseSensitive: false),
|
||
'',
|
||
).trim();
|
||
|
||
content = content.replaceAll(
|
||
RegExp(r'(?:\n|\r\n)\s*(?:Метрика|Количество символов|Символов|Длина текста)[:—–\s][\s\S]*$', caseSensitive: false),
|
||
'',
|
||
).trim();
|
||
|
||
if (content.isNotEmpty) {
|
||
if (!blocks.containsKey(slot) || content.length > blocks[slot]!.length) {
|
||
blocks[slot] = content;
|
||
}
|
||
}
|
||
}
|
||
|
||
return blocks;
|
||
}
|
||
|
||
final result = parseRevisionBlocks(input);
|
||
expect(result.length, 5);
|
||
expect(result.containsKey('ЯДРО'), true);
|
||
expect(result.containsKey('ОРАКУЛ'), true);
|
||
expect(result.containsKey('ТЕНЬ'), true);
|
||
expect(result.containsKey('ВОПРОС'), true);
|
||
expect(result.containsKey('ЖЕСТ'), true);
|
||
|
||
expect(result['ЯДРО']!.startsWith('Огонь — это первородная'), true);
|
||
expect(result['ОРАКУЛ']!.contains('Праиндоевропейский корень'), true);
|
||
expect(result['ТЕНЬ']!.contains('Теневая ловушка принципа'), true);
|
||
expect(result['ВОПРОС']!.contains('Что именно в твоей структуре'), true);
|
||
expect(result['ЖЕСТ']!.contains('Взять сухую щепку'), true);
|
||
expect(result['ЖЕСТ']!.contains('Метрика'), false);
|
||
});
|
||
}
|