From 317d929016fbbe0ab7d3d01db2c2416f6f5ba4e3 Mon Sep 17 00:00:00 2001 From: Vitaly the Alpaca Date: Thu, 9 May 2024 11:31:21 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20OPI=20->=20OInt=20?= =?UTF-8?q?(workflow)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/oint_test_Airtable.yml | 4 + .github/workflows/oint_test_full.yml | 4 + OInt/core/Modules/OPI_Airtable.os | 142 ++++++++++++++++++ OInt/tests/Modules/internal/OPI_Тесты.os | 67 +++++++++ .../Modules/OPI_ПолучениеДанныхТестов.os | 1 + .../internal/Modules/OPI_Инструменты.os | 21 ++- 6 files changed, 237 insertions(+), 2 deletions(-) diff --git a/.github/workflows/oint_test_Airtable.yml b/.github/workflows/oint_test_Airtable.yml index 54dbb3125..ab9ee7c2d 100644 --- a/.github/workflows/oint_test_Airtable.yml +++ b/.github/workflows/oint_test_Airtable.yml @@ -91,6 +91,10 @@ jobs: if: ${{ cancelled() }} == false run: oscript /home/runner/.local/share/ovm/current/lib/1testrunner/src/main.os -run ./OInt/tests/Modules/internal/OPI_Тесты.os "АТ_СоздатьПоле" + - name: Создать/Удалить записи + if: ${{ cancelled() }} == false + run: oscript /home/runner/.local/share/ovm/current/lib/1testrunner/src/main.os -run ./OInt/tests/Modules/internal/OPI_Тесты.os "АТ_СоздатьУдалитьЗаписи" + - name: Записать измененные данные if: ${{ cancelled() }} == false diff --git a/.github/workflows/oint_test_full.yml b/.github/workflows/oint_test_full.yml index e71332a89..483a5966c 100644 --- a/.github/workflows/oint_test_full.yml +++ b/.github/workflows/oint_test_full.yml @@ -851,6 +851,10 @@ jobs: if: ${{ cancelled() }} == false run: oscript /home/runner/.local/share/ovm/current/lib/1testrunner/src/main.os -run ./OInt/tests/Modules/internal/OPI_Тесты.os "АТ_СоздатьПоле" + - name: Создать/Удалить записи + if: ${{ cancelled() }} == false + run: oscript /home/runner/.local/share/ovm/current/lib/1testrunner/src/main.os -run ./OInt/tests/Modules/internal/OPI_Тесты.os "АТ_СоздатьУдалитьЗаписи" + Encode: runs-on: ubuntu-latest needs: [Testing-Twitter] diff --git a/OInt/core/Modules/OPI_Airtable.os b/OInt/core/Modules/OPI_Airtable.os index c6dd3f499..c464e2a2c 100644 --- a/OInt/core/Modules/OPI_Airtable.os +++ b/OInt/core/Modules/OPI_Airtable.os @@ -292,6 +292,128 @@ #КонецОбласти +#Область РаботаСЗаписями + +// Получить список записей +// Получает список записей выбранной таблицы +// +// Параметры: +// Токен - Строка - Токен - token +// База - Строка - Идентификатор базы данных - base +// Таблица - Строка - Идентификатор таблицы - table +// Отступ - Строка - Иднтификатор следующей страницы данных из предыдущего запроса - offset +// +// Возвращаемое значение: +// Соответствие Из КлючИЗначение - сериализованный JSON ответа от Airtable +Функция ПолучитьСписокЗаписей(Знач Токен, Знач База, Знач Таблица, Знач Отступ = "") Экспорт + + OPI_ПреобразованиеТипов.ПолучитьСтроку(База); + OPI_ПреобразованиеТипов.ПолучитьСтроку(Таблица); + + URL = "https://api.airtable.com/v0/" + База + "/" + Таблица; + Заголовки = ПолучитьЗаголовокАвторизации(Токен); + + Параметры = Новый Структура(); + OPI_Инструменты.ДобавитьПоле("offset", Отступ, "Строка", Параметры); + + Ответ = OPI_Инструменты.Get(URL, Параметры, Заголовки); + + Возврат Ответ; + +КонецФункции + +// Получить запись +// Получает данные строки таблицы по идентификатору +// +// Параметры: +// Токен - Строка - Токен - token +// База - Строка - Идентификатор базы данных - base +// Таблица - Строка - Идентификатор таблицы - table +// Запись - Строка - Идентификатор записи в таблице - record +// +// Возвращаемое значение: +// Соответствие Из КлючИЗначение - сериализованный JSON ответа от Airtable +Функция ПолучитьЗапись(Знач Токен, Знач База, Знач Таблица, Знач Запись) Экспорт + + OPI_ПреобразованиеТипов.ПолучитьСтроку(База); + OPI_ПреобразованиеТипов.ПолучитьСтроку(Таблица); + OPI_ПреобразованиеТипов.ПолучитьСтроку(Запись); + + URL = "https://api.airtable.com/v0/" + База + "/" + Таблица + "/" + Запись; + Заголовки = ПолучитьЗаголовокАвторизации(Токен); + + Ответ = OPI_Инструменты.Get(URL, , Заголовки); + + Возврат Ответ; + +КонецФункции + +// Создать записи +// Создает одну или массив записей по описанию или массиву описаний значений полей +// +// Параметры: +// Токен - Строка - Токен - token +// База - Строка - Идентификатор базы данных - base +// Таблица - Строка - Идентификатор таблицы - table +// Данные - Структура, Массив из Структура - Набор или массив наборов пар Ключ : Значение > Поле : Показатель - data +// +// Возвращаемое значение: +// Соответствие Из КлючИЗначение - сериализованный JSON ответа от Airtable +Функция СоздатьЗаписи(Знач Токен, Знач База, Знач Таблица, Знач Данные) Экспорт + + OPI_ПреобразованиеТипов.ПолучитьСтроку(База); + OPI_ПреобразованиеТипов.ПолучитьСтроку(Таблица); + OPI_ПреобразованиеТипов.ПолучитьКоллекцию(Данные); + + Параметры = Новый Структура(); + ДобавитьОписаниеДанных(Данные, Параметры); + + URL = "https://api.airtable.com/v0/" + База + "/" + Таблица; + Заголовки = ПолучитьЗаголовокАвторизации(Токен); + + Ответ = OPI_Инструменты.Post(URL, Параметры, Заголовки); + + Возврат Ответ; + +КонецФункции + +// Удалить записи +// Удаляет одну или массив записей по идентификаторам +// +// Параметры: +// Токен - Строка - Токен - token +// База - Строка - Идентификатор базы данных - base +// Таблица - Строка - Идентификатор таблицы - table +// Записи - Строка, Массив из Строка - Идентификатор или массив индентификаторов записей - records +// +// Возвращаемое значение: +// Соответствие Из КлючИЗначение - сериализованный JSON ответа от Airtable +Функция УдалитьЗаписи(Знач Токен, Знач База, Знач Таблица, Знач Записи) Экспорт + + OPI_ПреобразованиеТипов.ПолучитьСтроку(База); + OPI_ПреобразованиеТипов.ПолучитьСтроку(Таблица); + OPI_ПреобразованиеТипов.ПолучитьМассив(Записи); + + СтрокаЗаписей = ""; + + Для Каждого Запись Из Записи Цикл + СтрокаЗаписей = СтрокаЗаписей + + ?(ЗначениеЗаполнено(СтрокаЗаписей), "&", "?") + + "records[]=" + + OPI_Инструменты.ЧислоВСтроку(Запись); + КонецЦикла; + + URL = "https://api.airtable.com/v0/" + База + "/" + Таблица + СтрокаЗаписей; + Заголовки = ПолучитьЗаголовокАвторизации(Токен); + + Ответ = OPI_Инструменты.Delete(URL, , Заголовки); + + Возврат Ответ; + +КонецФункции + +#КонецОбласти + #КонецОбласти #Область СлужебныеПроцедурыИФункции @@ -331,4 +453,24 @@ КонецФункции +Процедура ДобавитьОписаниеДанных(Знач Данные, Параметры) + + Если ТипЗнч(Данные) = Тип("Массив") Тогда + + МассивОтправки = Новый Массив; + + Для Каждого ОписаниеЗаписи Из Данные Цикл + МассивОтправки.Добавить(Новый Структура("fields", ОписаниеЗаписи)); + КонецЦикла; + + OPI_Инструменты.ДобавитьПоле("records", МассивОтправки, "Массив", Параметры); + + Иначе + + OPI_Инструменты.ДобавитьПоле("fields", Данные, "Коллекция", Параметры); + + КонецЕсли; + +КонецПроцедуры + #КонецОбласти diff --git a/OInt/tests/Modules/internal/OPI_Тесты.os b/OInt/tests/Modules/internal/OPI_Тесты.os index 19fcddf53..4725818e0 100644 --- a/OInt/tests/Modules/internal/OPI_Тесты.os +++ b/OInt/tests/Modules/internal/OPI_Тесты.os @@ -3574,6 +3574,73 @@ КонецПроцедуры +Процедура АТ_СоздатьУдалитьЗаписи() Экспорт + + Токен = OPI_ПолучениеДанныхТестов.ПолучитьПараметр("Airtable_Token"); + База = OPI_ПолучениеДанныхТестов.ПолучитьПараметр("Airtable_Base"); + Таблица = OPI_ПолучениеДанныхТестов.ПолучитьПараметр("Airtable_Table"); + + Числовой = 10; + Строчный = "Привет"; + + ОписаниеСтроки1 = Новый Структура("Номер,Строковое", Числовой, Строчный); + ОписаниеСтроки2 = Новый Структура("Номер,Строковое", Числовой, Строчный); + + МассивУдаляемых = Новый Массив; + МассивОписаний = Новый Массив; + МассивОписаний.Добавить(ОписаниеСтроки1); + МассивОписаний.Добавить(ОписаниеСтроки2); + + Результат = OPI_Airtable.СоздатьЗаписи(Токен, База, Таблица, МассивОписаний); + + OPI_ПолучениеДанныхТестов.ЗаписатьЛог(Результат, "СоздатьЗаписи"); + + OPI_ПолучениеДанныхТестов.ОжидаетЧто(Результат["records"]).ИмеетТип("Массив"); + OPI_ПолучениеДанныхТестов.ОжидаетЧто(Результат["records"].Количество()).Равно(2); + + Для Каждого Запись Из Результат["records"] Цикл + МассивУдаляемых.Добавить(Запись["id"]); + КонецЦикла; + + Результат = OPI_Airtable.СоздатьЗаписи(Токен, База, Таблица, ОписаниеСтроки1); + + OPI_ПолучениеДанныхТестов.ЗаписатьЛог(Результат, "СоздатьЗаписи (одна)"); + + ОдиночнаяЗапись = Результат["id"]; + OPI_ПолучениеДанныхТестов.ОжидаетЧто(ОдиночнаяЗапись).Заполнено(ОдиночнаяЗапись); + OPI_ПолучениеДанныхТестов.ОжидаетЧто(Результат["createdTime"]).Заполнено(); + OPI_ПолучениеДанныхТестов.ОжидаетЧто(Результат["fields"]["Номер"]).Равно(Числовой); + OPI_ПолучениеДанныхТестов.ОжидаетЧто(СокрЛП(Результат["fields"]["Строковое"])).Равно(Строчный); + + Результат = OPI_Airtable.ПолучитьЗапись(Токен, База, Таблица, ОдиночнаяЗапись); + + OPI_ПолучениеДанныхТестов.ЗаписатьЛог(Результат, "ПолучитьЗапись"); + + OPI_ПолучениеДанныхТестов.ОжидаетЧто(Результат["id"]).Равно(ОдиночнаяЗапись); + + Результат = OPI_Airtable.ПолучитьСписокЗаписей(Токен, База, Таблица); + + OPI_ПолучениеДанныхТестов.ЗаписатьЛог(Результат, "ПолучитьСписокЗаписей"); + + OPI_ПолучениеДанныхТестов.ОжидаетЧто(Результат["records"]).ИмеетТип("Массив"); + OPI_ПолучениеДанныхТестов.ОжидаетЧто(Результат["records"].Количество()).Равно(3); + + Результат = OPI_Airtable.УдалитьЗаписи(Токен, База, Таблица, МассивУдаляемых); + + OPI_ПолучениеДанныхТестов.ЗаписатьЛог(Результат, "УдалитьЗаписи"); + + OPI_ПолучениеДанныхТестов.ОжидаетЧто(Результат["records"]).ИмеетТип("Массив"); + OPI_ПолучениеДанныхТестов.ОжидаетЧто(Результат["records"].Количество()).Равно(2); + + Результат = OPI_Airtable.УдалитьЗаписи(Токен, База, Таблица, ОдиночнаяЗапись); + + OPI_ПолучениеДанныхТестов.ЗаписатьЛог(Результат, "УдалитьЗаписи (одна)"); + + OPI_ПолучениеДанныхТестов.ОжидаетЧто(Результат["records"]).ИмеетТип("Массив"); + OPI_ПолучениеДанныхТестов.ОжидаетЧто(Результат["records"].Количество()).Равно(1); + +КонецПроцедуры + #КонецОбласти #КонецОбласти diff --git a/OInt/tools/Modules/OPI_ПолучениеДанныхТестов.os b/OInt/tools/Modules/OPI_ПолучениеДанныхТестов.os index 4b924b50f..d7d26cbd7 100644 --- a/OInt/tools/Modules/OPI_ПолучениеДанныхТестов.os +++ b/OInt/tools/Modules/OPI_ПолучениеДанныхТестов.os @@ -200,6 +200,7 @@ НовыйТест(ТаблицаТестов, "АТ_СоздатьБазу" , "Создать/Изменить базу" , АирТ); НовыйТест(ТаблицаТестов, "АТ_СоздатьТаблицу" , "Создать/Изменить таблицу" , АирТ); НовыйТест(ТаблицаТестов, "АТ_СоздатьПоле" , "Создать/Изменить поле" , АирТ); + НовыйТест(ТаблицаТестов, "АТ_СоздатьУдалитьЗаписи" , "Создать/Удалить записи" , АирТ); Возврат ТаблицаТестов; diff --git a/OInt/tools/Modules/internal/Modules/OPI_Инструменты.os b/OInt/tools/Modules/internal/Modules/OPI_Инструменты.os index e7d626fe5..9d1a1b975 100644 --- a/OInt/tools/Modules/internal/Modules/OPI_Инструменты.os +++ b/OInt/tools/Modules/internal/Modules/OPI_Инструменты.os @@ -184,11 +184,13 @@ СтрокаПараметров = "?"; Для Каждого Параметр Из Параметры Цикл + + ЗначениеПараметра = ПреобразоватьПараметрВСтроку(Параметр.Значение); + СтрокаПараметров = СтрокаПараметров + Параметр.Ключ + "=" - + КодироватьСтроку(Параметр.Значение, - СпособКодированияСтроки.КодировкаURL) + + ЗначениеПараметра + "&"; КонецЦикла; @@ -704,6 +706,21 @@ КонецФункции +Функция ПреобразоватьПараметрВСтроку(Знач Значение) + + Если ТипЗнч(Значение) = Тип("Массив") Тогда + Значение = СтрСоединить(Значение, ","); + Значение = КодироватьСтроку(Значение, СпособКодированияСтроки.КодировкаURL); + Значение = "[" + Значение + "]"; + Иначе + Значение = ЧислоВСтроку(Значение); + Значение = КодироватьСтроку(Значение, СпособКодированияСтроки.КодировкаURL); + КонецЕсли; + + Возврат Значение; + +КонецФункции + Процедура УстановитьТелоЗапроса(Запрос, Знач Параметры, Знач JSON) Коллекция = ТипЗнч(Параметры) = Тип("Структура") From 0e182b810f8f762a79f662806257180a8228ff71 Mon Sep 17 00:00:00 2001 From: Vitaly the Alpaca Date: Thu, 9 May 2024 11:35:57 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B7=D0=B0=D1=88=D0=B8=D1=84=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20=D0=BF=D0=BE=20=D1=80=D0=B5=D0=B7=D1=83=D0=BB?= =?UTF-8?q?=D1=8C=D1=82=D0=B0=D1=82=D0=B0=D0=BC=20=D1=82=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=20(workflow)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data.json.gpg | Bin 2773 -> 2776 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/data.json.gpg b/data.json.gpg index ac9b01e32cd787b8898338e434d952ca49c301d0..40265afaaf59146783525660c779be4cbd3c3201 100644 GIT binary patch literal 2776 zcmV;}3Mci94Fm}T0{P%^+Rx{SNB`360sUKTrD}`tajfuGKruC47Ll2?kPJ$!wvd_= zW-F9o8tw1%vsfO+AkkxSQ^bget78pOQEDkY_&9b?>WZW2&Z;RRv0HfnMc<$le;t!mHSxoKi!XU`SY2LF}GTl zwC%?-9Du>8G_GYRv0cVz*UWge_FVmS#xv5&VNZqqx=BU9=E>bms4>v(Ii$>0(RZgM zK4S+HjN@)Az9@XADFOQYF7I~z(e?H>K`G2ls#P!11Y*(A)yq!Y<(kE22I`k#%)7k& z*8|`JzrZizTU|}EN#U!uqPTR&3xn}(?^H6YUQgm92?UL0Kc2dQ_9q5zPf^CB>THz0 zl~wQQ^f8PDiO@3A)&`nLa*{@-8r@g}7{Y?IH^x5)vQNbN!mv1)*caO4oJx&g;ecy>r)6XnAD1 zO6r}yAQ4P?vqb6useRVf4L|x9pNcW_KOGnkw$XlsCY4i1<{5OR*#F~e;}UvJEwZh4 z^t7n=!e*t=4l#0grcg`VK?l~3r`ze`&U-6k{0-`_)nLSFdn-`r z#c$RzBC^>bVPXMhO zcan%R1niADQ1#-XKW^CL*Y8y*TPze&wXJM`;-LrDxK(x@zn2JZ=w~2{;^+B9nv~?Z z`U_v*S8>j!PQk64@C&lw| zu_8If5iXp}eci)Pl|Ima|51`0WiUVN=J?98e9!VDu1C8_TQ_n9EUd)cfuQxy+MVF( zQ!W2X8fQOLYXJB4u>=*r5{|_*sumg}_?yLu65W9S?dciLbNe$$26In5I~|Y>d~+?y zliRv*6Dc5?Tac?PFGb0TkU-7(SmPG~24{GY6GJ&0(*%O;iSE%@+bi`ii=;%16<=-sz8qTn&W&gwIaLI-5Tm{EZoV~*t^Z(e`DE*uN!XXiuV{P;Y~KgxVP7 z6=q~t-tHJ{(M z0Bv9NWWkvA=ex7a?tOI5k|cp6uMWTR@-(TL{CXqkh_Oc)ab>rG4KJ?;_rOk)70E2aUBB9x_D;T{D97E$U^?K424(l7 zy?mPjW=6E+Yy~KEyC1)9miuP1I7q*zcjTi-7?5}({6B(wZz~V_Jbpc`t|XZV#-I`r z$4}dre#XI+htX8tXHQeOu|k?Mf<3qEImQ80nnnx1`9M@7xebxGOHI>!(J`8fn{%CZ zbnd!ksJ4%~|G$T7tj9w}V+QgrDr7CogDFgu*49-{FVT5>gf-NLZ z+pvPUbjo9Ot!~L!#gY^XiZJOZgUW>}y)< zu(qlEa>U%b<7Ih1C-c^h^G29uZMrLrM z#u1^ZCh(ufx1sX9sfJSDR)Ot(AHaPp^L>6ViYL59BS&ju7}xIif840d6T7;zYgA`` z?ux&74{?8Zu>joq=$A%K38O#7b=IES2NPYf-}2{V)hTfhb6XYknHGO!b#@+8T^mGU zO}vM_AR{ao9``-45^vs|ieP$ZlOhhPRo|G2Ij8V%`oh-J%Ozu{*DSKhJdWbf6WFb0 zTbwT!ek3)cN-kD37M+bm>(J_k9W$h)vcSieT0>hzQ%+9BiD6+>pTZ6O+r$xAj9GS7ZFIp#%g&`2AaVl*HbEmEDWWm z=s;6+D7V-VtiCV?baJez)PMG<5L(|OM@0wqK`vmC!9XXa<`*HeG7)Vrvc+ zPCNWVtV>jS$#E{!MgG$Ykz5qUVx804V&JvzC2nnfaq9teK24!`rc908$)BIb~fNDZ0bmYL`foIGB#Tp~icZ}c~)L$~)@H4x|QWmn5?O3341^Rrx` zOIB754rzJWZ%;b}&xZwl(XF068w)7C<@Avy0h{kLoxn0s_}eBZO9;#-M8)cYXIITF ep-?iHI0MzEC~8MEs0*rd3=yijao_9k7nsPF2Y@#K literal 2773 zcmV;`3M%!C4Fm}T0%dx=X#(sd8UNDj0VyHXYDO8#cQS4FxT%9=YC)(w=V{4t%-0%G z*P4n+w(U!<-TYm?hF`=>o}cmCKdfl&0PQxEOok`_)G~!qu-+NR~Z;EC!X`D zIa$EB;w(AhzNS-jt7w{kY4%&Bfb6hKx&aKhL^Poh)s8W{x$+V8&aG(*QPLrO(PcJ#d?;^wI> z4#+iawucOW?ZAvgyV8vX%%cnylU4StQzq{D5l`qU zkS@~0aFdC@POcTj#CjX$=WJgQGD?Z6K3vKdQ-bEbqxPKpHQywDFr78qzY`o(t%_9<>#ha3;|vQC&GH zR4V{+W!mp}(}w3Om;5_-ei0HZKh?JC)L7^kn5%UDXp_oFr&M$8k~JQXgPd*_BOA?A zN#G!v(GX*omNUd{=!yd}cwku4k_q`S@Xu$8%H^R)8Ek|=vIGjg0#z6lcGerGf;_0y zPqj}NLzWrYu7%#oaMBl?-!VG6>R~fnnDhPUUX3|3qUmO+wy8w9?PE^w-XDu1W{FuJ zyOkf<7Tsf|RN-YKsRqq*y)2?s{^V14B`q;8?P<`hL~toAV>1+)a=%yqpjfO^eSGf52FM*%Wp3= z!6=0If})#wM}3JWiI0|+O*L{WhQ~Br?P2B<+w*j11l4}BHvD;JxgG@*7A)>On@}UE z8g{$6vtWl<8WLtGvsVqJ#s72C18%b$Cl{Sv=+&gTd0e@{Sui2L53_x&nv3t6+ZyZNFOmS0t2(}~yvs~e$<<&@{BnFu(CfvmH8@!*RrBMrdWBtFyz%MbN8j9G=9Kb{W5#|d<%6=nJv{Kn}%DCpx=fzg^&60bCr*e#xG%$U-SCMo_8FSWGnT#Q1Od^Y53R` zx6JNuaI=`gqvbH63?duq1WDzUvPkTU=2TKu547~Z{TrcjnWY~4+u>Hxh!J;C>PolP zqbb5vu!{yhm_ZZQlSo*9`EG)juFtc&W zGwdzs{H-9Pm@jkK2cK-m_}Wqy6$sY#|9cO+fAi#T=DS8BWs-TOaWKg%F=u) z?wRG*pEYWS-E1z34B>&)?_SzJzVr-0%rqlL)&Q+FtEhLGm?$#qHNFVm1&jw6%=?0e~2JkswxU~_x?I9)LEG|ZDh$7%h^pSkH_oFcB!29ynk8`o_ z>kIad`#~=i*Zv7fT);ytmU_g$pbY_8KI`DWkDnZx%7#DN-JOR(xUq=3MVBPeA|Y|c zBx~FBnP7l4(1>5_Q&*iUGU*8Q5&qFY)vzrWQ-ggD;$!MxSai?AdNVFocxiCP4Q09i z%Q7l+>h-X=0TQC(8NfZT>`vrsB#<8SLLqZ+Y+0R4*I6sgK>|%=Ar0yJ6L?N~_`1av z$(3O-irT&cXezL_5Dm^~M^Y`+uNA&~X5+_$XL0ZDLYbI`uuxr$pd5r_as2980)FuDk~ zwzZ{;2ojZ!9fO?+Ba#aSl&OYr8HFB9G)$#UMxHIJJ=7d&1^xjx=TC4-5jjxFPV*vK zPCz1yBdw8MQA;nWZR$dOoJqOf0e6mV4?M&+JI|SqX!?pE7#(o|%o_ZRKi2DxqwOn^jSGXRI$6xqDyvvZnY;rCzals?q&$Y*zxS%d|Uke zK7Kc)L@6g|l_sNlwV;nSO`&934*Tw^!`(Eq2=ZzO^_pq#;J^X2IUGtG{$q!F*%C>C zy+7}Iou4p|IvFw#2PpYLUn>z((2ono*#TUVbOblEegE4>H?mr@w-Gb@1RP5AH}KL! z