/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/encoding/ini.nv
130 строк
5 KB
Evgeniy Golovin
std(naming): collections/hashmap → hash_map — единственный нарушитель snake_case-правила модулей
06 авг 2026, 12:48
06 авг 2026, 12:48
0b0a260
Код
Авторство
О чём код?
// stdlib/ini.nv — простой INI parser. // // Формат: // ; comment // # тоже comment // [section] // key = value // key2 = value2 // // Не имеет официального RFC, формат тривиальный. Поддерживает: // - sections в [...] (пустая section = top-level) // - key = value (whitespace вокруг = игнорируется) // - комментарии # и ; // - quoted values "..." (опционально) // // API: // Ini.parse(s str) Fail[ParseIniError] -> HashMap[str, HashMap[str, str]] // // Структура: outer map от section name → inner map от key → value. // Top-level keys (вне sections) — в section с именем "". module encoding.ini import std.collections.hash_map.{HashMap} /// Errors while parsing INI. #stable(since = "0.1") export type ParseIniError enum | InvalidLine { line int, content str } | UnclosedSection { line int } /// Parse an INI document → `HashMap[section, HashMap[key, value]]`. Keys in the default /// (sectionless) part are stored under the key `""`. #stable(since = "0.1") export fn Ini.parse(s str) Fail[ParseIniError] -> HashMap[str, HashMap[str, str]] { // [M-lint-findings-static-conversion] mut result = HashMap[str, HashMap[str, str]].new() result.insert("", HashMap[str, str].new()) mut current_section = "" mut line_num = 0 for raw_line in s.split("\n") { line_num += 1 ro line = raw_line.trim_ascii() // Пропускаем пустые и комментарии if line.len() == 0 { continue } if line.starts_with("#") || line.starts_with(";") { continue } // Section header if line.starts_with("[") { if !line.ends_with("]") { throw UnclosedSection { line: line_num } } current_section = line[1..line.len() - 1].trim_ascii() if !result.contains(current_section) { result.insert(current_section, HashMap[str, str].new()) } continue } // Key = value match line.find("=") { None => throw InvalidLine { line: line_num, content: line } Some(i) => { ro key = line[..i].trim_ascii() ro value_raw = line[i + 1..].trim_ascii() ro value = if value_raw.starts_with("\"") && value_raw.ends_with("\"") && value_raw.len() >= 2 { value_raw[1..value_raw.len() - 1] } else { value_raw } match result.get(current_section) { // [M-ro-launder-pattern-bind-not-enforced] (реестр 221.1 // №106, D34): `mut` объявлен ПРЯМО в паттерне (D34-канон // `Ok(mut x)`) — раньше был bare bind + отдельный // `mut sm = section_map` cross-binding, отмывавший // readonly-заморозку кучевого HashMap (D246 ORACLE G); // launder-энфорс на паттерн-биндингах (№106) теперь это // ловит — прямой `mut` в паттерне устраняет и дыру, и // лишний промежуточный binding. Some(mut section_map) => { section_map.insert(key, value) result.insert(current_section, section_map) } None => () // unreachable: создали выше } } } } result } // Сериализация обратно в INI. /// Encode `HashMap[section, HashMap[key, value]]` → INI string. Default section /// (key `""`) is emitted without a `[section]` header above it. #stable(since = "0.1") export fn Ini.encode(data HashMap[str, HashMap[str, str]]) -> str { consume buf = StringBuilder.new(cap: 256) // Сначала top-level ("") match data.get("") { Some(top) => { for entry in top.iter() { buf.append(entry.0) buf.append(" = ") buf.append(entry.1) buf.append('\n') } } None => () } // Потом sections for entry in data.iter() { if entry.0 == "" { continue } buf.append('\n') buf.append('[') buf.append(entry.0) buf.append(']') buf.append('\n') for kv in entry.1.iter() { buf.append(kv.0) buf.append(" = ") buf.append(kv.1) buf.append('\n') } } buf } // Тесты — см. peer-файл ini_test.nv (module encoding.ini_test).