Tesseract OCR tips — создание своего словаря для повышения эффективности OCR

Это мой первый пост об оптическом распознавании текста (OCR) с использованием Tesseract. Tesserast это очень популярная open source библиотека для OCR поддерживаемая Google, которая дает высокие результаты точности и поддерживает более 100 языков. В этом посте я расскажу как можно работать со стандартным словарем для языковой модели Tesseract и настроить его под свои нужды. Кому интересно, прошу под кат.

Языковые модели и словари Tesseract

Для распознавания текста на конкретном языке Tesseract использует языковые модели и словари. Языковая модель содержит в себе значения параметров модели нейронной сети и другие данные обучения. Например, языковая модель для английского языка хранится в файле eng.traineddata. Пользователь может создать свой список слов для Tesseract так, чтобы Tesseract мог научиться их распознавать.

Tesseract позволяет расширять стандартный словарь для любого поддерживаемого языка добавлением собственных слов либо обучить языковую модель полностью заменив слова стандартного словаря своими словами.

Tesseract использует специальные файлы .dawg для различных категорий слов в словаре. Например, файл .word-dawg используется для основных слов словаря, а файл freq-dawg — для наиболее часто встречающихся слов. Более подробную информацию по вопросу можно найти здесь.

Кастомизация языковой модели Tesseract

Данное руководство можно применить для версий Tesseract 3.0.5 и 4.0.0. Единственное отличие Tesseract 4.0.0 от более ранней 3.0.х в том, что в версии 4 в основе Tesseract лежит модель LSTM и файлы словаря dawg имеют расширение lstm—dawg (в версии v3.0.5 они имеют расширение -dawg). Так например файл наиболее часто встречающихся слов теперь имеет расширение lstm-freq-dawg вместо freq-dawg, а файл unicharset получил расширение lstm-unicharset (ранее .unicharset).

Для начала установим библиотеку Tesseract OCR. В этом туториале я использую ОС Ubuntu (я использовал Ubuntu 18.04) и Tesseract v4. Просто установим Tesseract с помощью пакета apt:

sudo apt update && sudo apt install tesseract-ocr 

Кроме самой библиотеки Tesseract эта команда также установит все необходимые инструменты для обучения языковой модели (training tools).

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

Затем перейдет в папку tessdata. Нам нужны права root для выполнения операций в этой системной папке

sudo su 

Скопируем файл wordlistfile в эту папку. Распакуем файл языковой модели eng.traineddata в папку traineddat_backup

combine_tessdata -u eng.traineddata traineddat_backup/eng. 

Эта команда извлекает все файлы, необходимые для компиляции языковой модели в папку traineddat_backup.

Теперь создадим файл eng.lstm-word-dawg из нашего файла wordlistfile с помощью утилиты wordlistfile

wordlist2dawg wordlistfile eng.lstm-word-dawg traineddat_backup/eng.lstm-unicharset 

и скомпилируем новый файл языковой модели eng.traineddata

combine_tessdata -o eng.traineddata eng.lstm-word-dawg 

Мы получим файл языковой модели eng.traineddata из наших собственных слов словаря.

Теперь обучим языковую модель eng полностью заменив слова стандартного словаря своими словами. Сначала нам нужно сделать бэкап всех файлов dawg (.lstm-word-dawg, .lstm-freq-dawg итд), находящихся в папке traineddat_backup, поскольку мы заменим их новыми. Просто создадим папку tmp и перенесем в нее все файлы dawg.

После этого скопируем наш файл eng.lstm-word-dawg, созданный ранее, в папку traineddat_backup. Перейдем в эту папку и скомпилируем новую языковую модель

combine_tessdata eng. </source Чтобы использовать новую языковую модель ее нужно скопировать в папку tessdata и дать любое название из трех букв (например cus - custom).   Чтобы проверить OCR с Tesseract на новой модели выполним команду:  <source lang="bash"> tesseract <image> -l <your_model> <output> 

где output — имя текстового файла для записи результата OCR или ‘stdout’ для вывода в терминал.

Файлы конфигурации в Tesseract OCR

Tesseract использует файлы конфигурации (простые текстовые файлы, содержащие переменные и их значения в виде «ключ — значение», разделенные пробелами), которые позволяют пользователю контролировать результат OCR. Вы можете создать собственную конфигурацию (myconf) и поместить ее в папку configs внутри папки tessdata и указать имя конфигурации при использовании Tesseract:

tesseract <image> <options> myconf 

где options: out — имя файла для вывода результата или ‘stdout’,-l — языковая модель, — psm — метод постраничной сегментации (Page segmentation method).

Tesseract предоставляет множество параметров управления для настройки вывода и повышения точности OCR. Так здесь есть переменные, контролирующие использование словарей, например исключение слов, которых нет в word_dawg / user_words (language_model_penalty_non_freq_dict_word и language_model_penalty_non_dict_word). Более подробно о параметрах управления можно прочитать здесь.

Я использовал такие значения в своем конфиге:

language_model_penalty_non_freq_dict_word 1 language_model_penalty_non_dict_word 1 

Это позволило распознать некоторые слова из моего словаря.

Пока на этом все. Всем удачи в использовании Tesseract для задачи OCR и до новых встреч!


ссылка на оригинал статьи https://habr.com/ru/post/466571/

Коммутаторы ExtremeSwitching X465. Универсальный гигабит и мультигигабит

Портфолио коммутаторов компании Extreme Networks пополнилось семейством ExtremeSwitching X465, линейка которых представлена шестью моделями с «медными» портами (релиз «оптики» ожидается в ближайшем будущем). По сути, ExtremeSwitching X465, является третьим поколением и логическим продолжением коммутаторов Summit X460 и Summit X460G2. «Универсальными» они являются в том плане, что инсталлировать их можно на любой уровень сети, — как на доступ, так и в ядро или распределение. Ранее мы уже писали о «РоЕ++», «Insight architecture», «Extreme Extended Edge/VPEX», — собственно Х465 это первые коммутаторы, которые поддерживают все эти технологии одновременно.


Линейка состоит из четырех гигабитных и двух мультигигабитных коммутаторов, все модели поддерживают стекирование. При этом только мультигигабитные модели поддерживают «Insight architecture», и поэтому имеют увеличенные RAM и CPU

Условные обозначения:
«T» — «Base-T» (обычный медный доступ)
«P» — PoE+ (802.3at)
«W» — PoE++ (802.3bt)
«MU» — MultiGigabit

Высокобюджетный РоЕ++ вместе с мультискоростными 100М/1G/2,5G/5G портами доступа, позволяют подключать Wave 2 или WiFi 6 AP, PoS терминалы, современные LED светильники или IP камеры с поворотным зумом, устройства IoT, или же заложить инфраструктуру для подключения устройств доступа следующих поколений. Коммутаторы Х465 также поддерживают «Fast», и «Perpetual-PoE», при этом общий бюджет РоЕ некоторых моделей может составлять до 3500W.

X465 могут использоваться в качестве «Control Bridge» для организации архитектуры «Extreme Extended Edge» и подключения расширителей портов V400/V300. При этом V300-я серия VPEX может быть запитана от X465 по PoE++ (90W или 60W)

К примеру, подключив и запитав 48 VPEX от коммутаторов X465, а также задействовав «Insight architecture» для настройки VM с firewall + NAT, или WiFi controller, можно значительно упростить сетевую архитектуру, а также сэкономить на силовой и другой капитальной инфраструктуре.

Из других ключевых особенностей ExtremeSwitching X465 можно выделить (а некоторые повторить):

—>Security Policy Edge
1. Highly scalable OnePolicy support
2. 128 bit/256 bit MACsec capable uplinks
3. 128 bit MACsec supported on 10/100/1000BaseT Access ports

—> IoT Edge
1. Multi-Gig access / 30/60/90W PoE
2. Extended Edge Switching Controlling Bridge
3. Perpetual/Fast PoE

—> Any Campus Edge
1. Fabric Attach -> Fabric Connect
2. Universal Attach -> Any Campus

—> Visibility Edge
1. Application Telemetry/sFlow
2. Insight Architecture

—> Timing Edge
1. Audio Video Bridging (AVB)

—> Provider Edge
1. G.8032/ERP
2. MPLS/VXLAN/EVPN

—> Bluetooth Dongle for console access
—> Uplink options include 4 x 10Gbps, 2 x 25Gbps, 4 x 25Gbps and 2 x 40Gbps

Даташит с более детальной информацией доступен по ссылке Х465 или на сайте extremenetworks.com.

Любые возникшие или оставшиеся вопросы, а также узнать о наличие Х465 для проведения тестирования всегда можно задать сотрудникам нашего офиса – cis@extremenetworks.com.


ссылка на оригинал статьи https://habr.com/ru/company/extremenetworks/blog/482570/

Криптографический АРМ на базе стандартов с открытым ключом для платформы Android

imageПришло время продемонстрировать как криптографический АРМ на базе стандартов с открытым ключом cryptoarmpkcs работает на одной из мобильных платформ, а именно Android.
Концепция, которая закладывалась при разработке утилиты cryptoarmpkcs, состоит в том, чтобы пользователь просто должен минимум неудобств при создании и проверке электронной подписи. Именно поэтому мы предлагаем в качестве ключевого носителя для личного сертификата использовать криптографические токены PKCS#11 и/или защищенные контейнеры PKCS#12. Надо сказать, что использование PKCS#12 во многих случаях оказывается чуть ли не единственно возможным, например, когда у тех же токенов PKCS#11 не оказывается поддержки для тех или иных платформах. Вот и мы решили начать портирование утилиты cryptoarmpkcs на платформу Android с поддержки защищенных контейнеров PKCS#12.
Сразу отметим, что поскольку проект делался на платформе C и Tcl/Tk, то портирование не вызвало каких-либо принципиальных трудностей. Это стало возможным еще благодаря технологии Androwish. С разворачиванием и настройкой среды никаких проблем не возникло (моя рабочая среда это Linux — Mageia-7.0). Приведу только один скрипт, для разворачивания Android Command Line Tools (sdk-tools-linux-xxxx.zip):

#!/bin/sh if [ $# -ne 1   ]     then  	echo "./InstallAndroidSDK.sh <sdk-tools-linux-xxxxxxxx.zip>" 	echo "Не указан архив или число параметров больше 1" 	exit 1 fi if [ ! -f $1 ]     then  	echo "./InstallAndroidSDK.sh <sdk-tools-linux-xxxxxxxx.zip>" 	echo "архив $1 отсутствует" 	exit 1 fi #Распаковываем SDK-TOOLS в папку tools #unzip sdk-tools-linux-4333796.zip unzip $1 #Создаем папку android-sdk-linux mkdir android-sdk-linux #Перемещвем папку tools в android-sdk-linux mv tools android-sdk-linux cd android-sdk-linux/tools/bin ./sdkmanager "platform-tools" "platforms;android-29"  

Самое замечательное то, что в среду Androwish входит два интерпретатора undrowish-xxx и vanillawish-xxx полностью идентичных по составу «балалаек» (пакетов), входящих собственно в состав Androwish. Различие между undrowish и vanillawish состоит в том, что в vanillawish бэкэнд на основе SDL / AGG / freetype:

Наличие этих двух утилит позволяет разрабатывать приложение без использования самого Android-а и его эмулятора с максимальным приближением к реальному устройству. В первую очередь это, конечно, undroidwish-xxx.
Собственно пользовательский пакет для Android собирается в среде AWSDK. Дерево вашего проекта должно перенесено а папку ~/AWSDK/assets/app. При этом главный модуль вашего проекта должен быть переименован в main.tcl. Если вы используете дополнительные балалайки с динамическими библиотеками, то библиотеки необходимо положить в папки ~/AWSDK/libs/x86 для эмулятора и ~/AWSDK/libs/armeabi для реального устройства.
После этого достаточно выполнить команду:

$wish ~/AWSDK/tools/bones

и следовать ее указаниям:

Собранный apk-пакет будет лежать в папке ~/AWSDK/build/outputs/apk.
Если у вас подключено реальное устройство или эмулятор, то пакет можно сразу установить.
При этом реальное устройство должно находиться в режиме отладки.
Но вернемся собственно к приложению. Что же пришлось в нем изменить.
Естественно, в первую очередь изменения связаны с размерами экрана. Пришлось перепроектировать главное (стартовое окно). В итоге, вместо одного окна на обычном компьютере:

появилось три окна на Android:

Первое окно является информационным. Условно оно разбивается на несколько частей. Первая часть, находящаяся вверху, содержит название утилиты и ее логотип.
Далее идет логотип производителя, информационная справка и завершают страницу три кнопки.
Страница написана с использованием компонента canvas (холста). На странице используются кнопки двух видов: одна в виде полупрозрачного прямоугольника (средняя), а две другие в виде прямоугольника с закругленными углами. Для создания кнопок с закругленными углами была задействована «балалайка» tkpath. Лично на меня эта балалайка произвела очень хорошее впечатление. Естественно при работе с холстом (canvas) львиную долю кода занимает геометрия. Ниже приводится скрипт create_titul_page.tcl для создания первой страницы приложения. Каждый желающий может его подредактировать под свое видение.

Скрипт create_titul_page.tcl

package require Tk package require tkpath 0.3.0  global mydir set mydir [file dirname [info script]] #Грузим картинки #Логотип продукта image create photo logo_product -file [file join $mydir "imageme" "validcertkey_51x24.png"]  #Логотип производителя image create photo logo_orel -file [file join $mydir "imageme" "я_орел_425x200.png"] -format "png -alpha 1.0" #Андроида с tcl/tk image create photo logo_and -file [file join $mydir "imageme" "AndTk_inv_147x173.png"] -format "png -alpha 1.0" #Свиток опечатанный image create photo svitok -file [file join $mydir "imageme" "blue_svitok.png"] -format "png -alpha 1.0" #Плитка image create photo tileand -file [file join $mydir "imageme" "tile_green_and_32x32.png"] -format "png -alpha 1.0" #Увеличить/уменьшить (отрицательное значение - уменьшение) proc scaleImage {im xfactor {yfactor 0}} {    set mode -subsample    if {$xfactor>=0 && $yfactor>=0} {        set mode -zoom    } else { 	set xfactor [expr $xfactor * -1]    }    if {$yfactor == 0} {set yfactor $xfactor}    set t [image create photo]    $t copy $im    $im blank    $im copy $t -shrink $mode $xfactor $yfactor    image delete $t }  proc createtile {w  backg} {     image create photo tiled     tiled copy $backg -to 0 0 $::scrwidth $::scrheight -shrink     $backg copy tiled     image delete tiled # Мостим холст     $w create image 0 0  \       -image $backg  \       -anchor nw }  proc create_rectangle  {canv img x1 y1 x2 y2 color alfa {wbd 0} {colorline black} } {     image create photo $img -format "default -colorformat  rgb"     set rgb1 [winfo rgb $canv $color]     set cr  [lindex $rgb1 0]     set cg  [lindex $rgb1 1]     set cb  [lindex $rgb1 2]     set fill [format "#%04x%04x%04x" $cr $cg $cb ] #Создаем цветной праямоугольник     $img put $fill -to 0 0 [expr {$x2 - $x1}] [expr {$y2 -$y1}] #Сохраняем картинку     set dimg [$img data -format png] #Создаем image с учетом alpha канала     image create photo $img -data $dimg -format "png -alpha $alfa" #    $img put [list $rgb1] -to 0 0 [expr {$x2 - $x1}] [expr {$y2 -$y1}] #Отображаем цветной прямоугольник     set imgr [$canv create image $x1 $y1 -image $img -anchor nw]      set cc [subst {butImg $img}]     $canv bind $imgr <ButtonPress-1> $cc #Оконтовка вокруг цветного прямоугольника     if {$wbd > 0 } { 	set item [$canv create rect $x1 $y1 $x2 $y2 -outline $colorline -width $wbd ] 	$canv bind $item <ButtonPress-1> $cc     }    return $imgr }  proc butImg {img} {     tk_messageBox -title "Кнопка" -icon info -message "Нажали кнопку=$img" -detail "::screenwidth=$::scrwidth\n::screenheight=$::scrheight" -parent .     if {$img == "exit"} { 	set answer [tk_dialog .dialog2 "Конец работы" "Вы действительно\nхотите выйти?" question 0 "Да" "Нет" ] 	if {$answer == 0} {     	    exit 	}	     } }  proc page_titul {fr  logo_manufacturer} {     global mydir #Создаем холст на весь экран     tkp::canvas $fr.can -borderwidth 0 -height [winfo screenheight .] -width [winfo screenwidth .] -relief flat #Мостим холст плиткой      createtile "$fr.can"  "tileand"     pack $fr.can  -anchor center -expand 1 -fill both -side top  -padx 0 -pady 0 #Вычисляем координаты для логотипа производителя #update чтобы обновилась информация в БД об окнах     update #    set aa [winfo height $fr.labtitul]     set aa $::padly #Центрируем логотип разработчика     set ha [image width $logo_manufacturer]     set xman [expr {($::scrwidth - $ha) / 2 }]     $fr.can create image $xman $aa -image $logo_manufacturer -anchor nw -tag tag_logo      set blogo [$fr.can bbox tag_logo]     set wexit [lindex $blogo 3]     if {$::typetlf } { 	set dlx [expr {$::padlx / 1}] 	$fr.can create text [expr $dlx + 6] [expr {$wexit + $::padly + 6}] -anchor nw -text "Электронная подпись" -fill black -font {{Roboto Condensed Medium} 15 } 	$fr.can create text $dlx [expr {$wexit + $::padly}] -anchor nw -text "Электронная подпись" -fill white -font {{Roboto Condensed Medium} 15 } -tag id_text0 	update 	set blogo [$fr.can bbox id_text0] 	set wexit [lindex $blogo 3] 	$fr.can create text [expr $dlx + 4] [expr {$wexit + $::padly + 4 - 80}] -anchor nw -text "для платформы Android" -fill black -font {{Roboto Condensed Medium} 13} 	$fr.can create text $dlx [expr {$wexit + $::padly - 80}] -anchor nw -text "для платформы Android" -fill white -font {{Roboto Condensed Medium} 13} -tag id_text1 	set blogo [$fr.can bbox id_text1] 	set wexit [lindex $blogo 3] 	$fr.can create text [expr $dlx + 3] [expr {$wexit + $::padly + 3 - 50}] -anchor nw -text "№ 63 ФЗ \"Об электронной\nподписи от 6 апреля 2011 года\"" -fill black -font {{Roboto} 10} 	$fr.can create text $dlx [expr {$wexit + $::padly - 50}] -anchor nw -text "№ 63 ФЗ \"Об электронной\nподписи от 6 апреля 2011 года\"" -fill white -font {{Roboto} 10} -tag id_text2 	set blogo [$fr.can bbox id_text2] 	set wexit [lindex $blogo 3] 	$fr.can create text [expr $dlx + 2] [expr {$wexit + $::padly + 2 - 40}] -text "Авторы: В.Н. Орлов\nhttp://soft.lissi.ru, http://www.lissi.ru\n+7(495)589-99-53\ne-mail: support@lissi.ru\n\n" \ 	-anchor nw -fill black  -font {{Roboto} 9} 	$fr.can create text $dlx [expr {$wexit + $::padly - 40}] -text "Авторы: В.Н. Орлов\nhttp://soft.lissi.ru, http://www.lissi.ru\n+7(495)589-99-53\ne-mail: support@lissi.ru\n\n" \ 	-anchor nw -fill white -tag id_text3  -font {{Roboto} 9}     } else { 	$fr.can create text [expr $::padlx + 2] [expr {$wexit + $::padly + 2}] -anchor nw -text "Электронная подпись" -fill black -font {{Nimbus Sans Narrow} 20} 	$fr.can create text $::padlx [expr {$wexit + $::padly}] -anchor nw -text "Электронная подпись" -fill white -font {{Nimbus Sans Narrow} 20} -tag id_text0 	set blogo [$fr.can bbox id_text0] 	set wexit [lindex $blogo 3] 	$fr.can create text [expr $::padlx + 2] [expr {$wexit + $::padly + 2 - 20}] -anchor nw -text "для платформы Android" -fill black -font {{Nimbus Sans Narrow} 18} 	$fr.can create text $::padlx [expr {$wexit + $::padly - 20}] -anchor nw -text "для платформы Android" -fill white -font {{Nimbus Sans Narrow} 18} -tag id_text1 	set blogo [$fr.can bbox id_text1] 	set wexit [lindex $blogo 3] 	$fr.can create text [expr $::padlx + 2] [expr {$wexit + $::padly + 2}] -anchor nw -text "№ 63 ФЗ \"Об электронной\nподписи от 6 апреля 2011 года\"" -fill black -font {{Nimbus Sans Narrow} 14} 	$fr.can create text $::padlx [expr {$wexit + $::padly}] -anchor nw -text "№ 63 ФЗ \"Об электронной\nподписи от 6 апреля 2011 года\"" -fill white -font {{Nimbus Sans Narrow} 14} -tag id_text2 	set blogo [$fr.can bbox id_text2] 	set wexit [lindex $blogo 3] 	$fr.can create text [expr $::padlx + 1] [expr {$wexit + $::padly + 1}] -text "Авторы: В.Н. Орлов\nhttp://soft.lissi.ru, http://www.lissi.ru\n+7(495)589-99-53\ne-mail: support@lissi.ru\n\n" \ 	-anchor nw -fill black  -font {{Nimbus Sans Narrow} 12} 	$fr.can create text $::padlx [expr {$wexit + $::padly}] -text "Авторы: В.Н. Орлов\nhttp://soft.lissi.ru, http://www.lissi.ru\n+7(495)589-99-53\ne-mail: support@lissi.ru\n\n" \ 	-anchor nw -fill white -tag id_text3  -font {{Nimbus Sans Narrow} 12} -tag id_text3 	set blogo [$fr.can bbox id_text2]     }     set blogo [$fr.can bbox id_text3]     set wland [lindex $blogo 3]     $fr.can create image $::padlx $wland -image logo_and -anchor nw -tag tag_land     set ha [image height logo_and]     set wa [image width logo_and]     set ha1 [expr {$ha - ($ha / 2 ) }]     $fr.can create image [expr {$wa - 80 }] [expr {$wland + $ha1}] -image svitok -anchor nw -tag tag_land     if {$::typetlf} { 	set x1 [expr {$::padlx / 2}] 	set y1 [expr {$wland + 120}] 	set x2 [expr {$::::scrwidth - $x1}] 	set y2 [expr {$y1 + 120}] 	set wd 8 	set rr 18     } else { 	set x1 [expr {$::padlx / 2}] 	set y1 [expr {$wland + 40}] 	set x2 [expr {$::::scrwidth - $x1}] 	set y2 [expr {$y1 + 40}] 	set wd 4 	set rr 6     }      set g5 [$fr.can gradient create linear -stops {{0 lightgreen} {1 green}}]      set S3 [$fr.can style create -stroke "skyblue" -fill  $g5 -strokewidth $wd  -fillopacity 0.6]     set im1 [$fr.can create prect $x1 $y1 $x2 $y2 -rx $rr -style $S3]     $fr.can bind $im1 <ButtonPress-1> {butImg "img1"} #Печатаем техт     set blogo [$fr.can bbox $im1]     set by2 [lindex $blogo 3]     set by1 [lindex $blogo 1]     set bb [expr {($by2 - $by1) / 2}]     set bx2 [lindex $blogo 2]     set bx1 [lindex $blogo 0]     set bbx [expr {($bx2 - $bx1) / 2}]     set txt1 [$fr.can create text [expr {$x1 + $::padlx * 2}] [expr {$y1 + 1 }] -anchor nw -text "Сайт разработчика" -fill black -font {{Arial} 10 normal}]  #Центрируем техт     set btxt1 [$fr.can bbox $txt1] #Смещение по оси Y     set ty2 [lindex $btxt1 3]     set ty1 [lindex $btxt1 1]     set tt [expr {$ty2 - $ty1}]     set tt [expr {$tt / 2}]     set offsy [expr {($by1 + $bb) - ($ty1 + $tt)}] #Смещение по оси X     set tx2 [lindex $btxt1 2]     set tx1 [lindex $btxt1 0]     set ttx [expr {$tx2 - $tx1}]     set ttx [expr {$ttx / 2}]     set offsx [expr {($bx1 + $bbx) - ($tx1 + $ttx)}]     $fr.can move $txt1 $offsx $offsy     $fr.can bind $txt1 <ButtonPress-1> {butImg "img1"}      if {$::typetlf} { 	set y1 [expr $y2 + 40] 	set x2 [expr {$::::scrwidth - $x1}] 	set y2 [expr {$y1 + 120}]     } else { 	set y1 [expr {$y1 + 60}] 	set x2 [expr {$::::scrwidth - $x1}] 	set y2 [expr {$y1 + 40}]     }     set im1 [create_rectangle $fr.can "but2" $x1 $y1 $x2 $y2 "#2b972d" 0.6 $wd "skyblue"] #Печатаем техт     set blogo [$fr.can bbox $im1]     set by2 [lindex $blogo 3]     set by1 [lindex $blogo 1]     set bb [expr {($by2 - $by1) / 2}]     set bx2 [lindex $blogo 2]     set bx1 [lindex $blogo 0]     set bbx [expr {($bx2 - $bx1) / 2}]     set txt1 [$fr.can create text [expr {$x1 + $::padlx * 2}] [expr {$y1 + 1 }] -anchor nw -text "Переход в основное меню" -fill black -font {{Roboto Condensed Medium} 12}]  #Центрируем текст     set btxt1 [$fr.can bbox $txt1] #Смещение по оси Y     set ty2 [lindex $btxt1 3]     set ty1 [lindex $btxt1 1]     set tt [expr {$ty2 - $ty1}]     set tt [expr {$tt / 2}]     set offsy [expr {($by1 + $bb) - ($ty1 + $tt)}] #Смещение по оси X     set tx2 [lindex $btxt1 2]     set tx1 [lindex $btxt1 0]     set ttx [expr {$tx2 - $tx1}]     set ttx [expr {$ttx / 2}]     set offsx [expr {($bx1 + $bbx) - ($tx1 + $ttx)}]     $fr.can move $txt1 $offsx $offsy     $fr.can bind $txt1 <ButtonPress-1> {butImg "but2"}     if {$::typetlf} { 	set x1 [expr {$::padlx / 2}] 	set y1 [expr $y2 + 40] 	set x2 [expr {$::::scrwidth - $x1}] 	set y2 [expr {$y1 + 120}]     } else { 	set x1 [expr {$::padlx / 2}] 	set y1 [expr {$y1 + 60}] 	set x2 [expr {$::::scrwidth - $x1}] 	set y2 [expr {$y1 + 40}]     }     set S3 [$fr.can style create -stroke skyblue -fill  $g5 -strokewidth $wd  -fillopacity 0.6]     set im1 [$fr.can create prect $x1 $y1 $x2 $y2 -rx $rr -style $S3]     set blogo [$fr.can bbox $im1]     $fr.can bind $im1 <ButtonPress-1> {butImg "exit"}     set by2 [lindex $blogo 3]     set by1 [lindex $blogo 1]     set bb [expr {($by2 - $by1) / 2}]     set bx2 [lindex $blogo 2]     set bx1 [lindex $blogo 0]     set bbx [expr {($bx2 - $bx1) / 2}]     set txt1 [$fr.can create text [expr {$x1 + $::padlx * 2}] [expr {$y1 + 1 }] -anchor nw -text "Конец работы" -fill black  -font {Arial 10 normal}]     $fr.can bind $txt1 <ButtonPress-1> {butImg "exit"}     set btxt1 [$fr.can bbox $txt1] #Смещение по оси Y     set ty2 [lindex $btxt1 3]     set ty1 [lindex $btxt1 1]     set tt [expr {$ty2 - $ty1}]     set tt [expr {$tt / 2}]     set offsy [expr {($by1 + $bb) - ($ty1 + $tt)}] #Смещение по оси X     set tx2 [lindex $btxt1 2]     set tx1 [lindex $btxt1 0]     set ttx [expr {$tx2 - $tx1}]     set ttx [expr {$ttx / 2}]     set offsx [expr {($bx1 + $bbx) - ($tx1 + $ttx)}]     $fr.can move $txt1 $offsx $offsy } #Собственно скрипт #Считываем размеры экрана set ::scrwidth [winfo screenwidth .] set ::scrheight [winfo screenheight .] set ::typetlf 0 #Проверяем, что это телефон if {$::scrwidth < $::scrheight} {     set ::typetlf 1 } set ::padls 20 set ::padlx 15 set ::padly 15 if {$::typetlf} { 	wm attributes . -fullscreen 1 	scaleImage icon_openfile_18x16 3 	scaleImage ::img::view_18x16 3 #Логотип производителя 	scaleImage logo_orel 2 #Логотип продуктв 	scaleImage logo_product 2 #Андроида tcl/tk 	scaleImage logo_and 3 #Свиток опечатанный 	scaleImage svitok 4 	set ::padls 50 	set ::padlx 75 	set ::padly 50 } else { #Конфигурирование виджета под смартфон     scaleImage logo_orel -2     set ::scrwidth 370     set ::scrheight 700     wm minsize . $::scrwidth $::scrheight     set geometr $::scrwidth     append geometr "x"     append geometr $::scrheight     append geometr "+0+0"     wm geometry . $geometr } #Создаем название продукта set name_product "CryptoArmPKCS-A"  label .labtitul -image logo_product -compound left -fg snow -text $name_product -font {Arial 10 bold} -anchor w  -width [winfo screenwidth .] -pady $::padls -padx 10 -bg #222222  pack .labtitul -anchor nw -expand 0 -fill x -side top  -padx 1 -pady 0 #Создаем стартовую страницу set i 0 ttk::frame .fr$i -pad 0 -padding 0 page_titul ".fr$i"  "logo_orel" pack .fr$i -side top -anchor center -expand 1 -fill both -side top  -padx 0 -pady 0  update

Для выполнения данного скрипта используем одну из утилит undroidwish или vanillawish:

$ /usr/local/bin64/undroidwish-e5dc71ed9d-linux64   create_titul_page.tcl 

или

$/usr/local/bin64/vanillawish-e5dc71ed9d-linux64  create_titul_page.tcl 

Результат представлен на первом скриншоте.

На второй странице перечисляется поддерживаемый утилитой cryptoarmpkcs-A функционал. Каждая строчка является кнопкой, при нажатии на которую будет отображена функциональная страница. Геометрия размещение кнопок на этой странице определяется шрифтом, который используется. Ниже приводится скрипт create_page_functions.tcl для создания второй/функциональной страницы приложения. Каждый желающий также может его подредактировать под свои функции.

Скрипт create_page_functions.tcl

package require Tk package require tkpath 0.3.0 global mydir set mydir [file dirname [info script]] #Увеличить/уменьшить (отрицательное значение - уменьшение) proc scaleImage {im xfactor {yfactor 0}} {    set mode -subsample    if {$xfactor>=0 && $yfactor>=0} {        set mode -zoom    } else { 	set xfactor [expr $xfactor * -1]    }     if {$yfactor == 0} {set yfactor $xfactor}    set t [image create photo]    $t copy $im    $im blank    $im copy $t -shrink $mode $xfactor $yfactor    image delete $t }  proc createtile {w  backg} {     image create photo tiled     tiled copy $backg -to 0 0 $::scrwidth $::scrheight -shrink     $backg copy tiled     image delete tiled # Мостим холст     $w create image 0 0  \       -image $backg  \       -anchor nw }  proc butCliked {num fr} {     pack forget  .fr1     set ::tekFrfunc $fr     pack $fr -side top -anchor center -expand 1 -fill both -side top  -padx 0 -pady 0      tk_dialog .dialog1 "Dear user:" "Button $num was clicked\nFr=$fr" info 0 OK  }  proc butImg {img} {     tk_messageBox -title "Кнопка" -icon info -message "Нажали кнопку=$img" -detail "::screenwidth=$::scrwidth\n::screenheight=$::scrheight" -parent .     if {$img == "exit"} { 	set answer [tk_dialog .dialog2 "Конец работы" "Вы действительно\nхотите выйти?" question 0 "Да" "Нет" ] 	if {$answer == 0} {     	    exit 	}	     } }  proc butReturn {} {     pack forget  $::tekFrfunc     pack .fr1 -side top -anchor center -expand 1 -fill both -side top  -padx 0 -pady 0  #    tk_dialog .dialog1 "Dear user:" "Button $num was clicked\nFr=$fr" info 0 OK  }  proc page_func {fr tile titul functions} { #Кнопки  меню     upvar $functions but #Создаем шрифт для кнопок     if {$::typetlf} { 	set feFONT_button "-family {Roboto} -size 9 -weight bold -slant roman" 	set widl 10     } else { 	set feFONT_button "-family {Arial} -size 12 -weight bold -slant roman" 	set widl 5     }     catch {font delete fontTEMP_drawer}     eval font create fontTEMP_drawer  $feFONT_button #Вычисляем максимальныю длину текста     set drawerCNT 0     set strMaxWidthPx 15     set Ndrawers [expr {[array size but] - 1}]     while { $drawerCNT <= $Ndrawers } { 	set strWidthPx [font measure fontTEMP_drawer "$but($drawerCNT)"] 	if { $strWidthPx > $strMaxWidthPx } {     	    set strMaxWidthPx $strWidthPx 	} 	incr drawerCNT     }     set drawerWidthPx [expr $strMaxWidthPx + 10]     set xxx [expr {($::::scrwidth - $drawerWidthPx) / 2}]      if {$fr != ".fr1"} { 	set hret [expr $::scrheight / 4]     } else { 	set hret $::scrheight     } 	set hret [expr $::scrheight / 4]     tkp::canvas $fr.can -borderwidth 0 -height $hret -width $::scrwidth -relief flat #Мостим холст плиткой      createtile "$fr.can"  $tile     pack $fr.can  -anchor center -expand 1 -fill both -side top  -padx 0 -pady 0     if {$titul != "" } { 	set allfunc $titul 	catch {font delete fontTEMP_titul} 	set font_titul "-family {Roboto Condensed Medium} -size 15"         eval font create fontTEMP_titul  $font_titul 	set funcWidthPx [font measure fontTEMP_titul "$allfunc"] 	set dlx [expr {($::::scrwidth - $funcWidthPx) / 2}]  	$fr.can create text [expr $dlx + 6] [expr {6 + 6}] -anchor nw -text "$allfunc" -fill black -font fontTEMP_titul 	$fr.can create text $dlx 6 -anchor nw -text "$allfunc" -fill white -font fontTEMP_titul -tag id_text0 	set blogo [$fr.can bbox id_text0] 	set boxbut [expr ([lindex $blogo 3] + 6 + 6)]     } else { 	set boxbut [expr 6 + 6]     } #Вычисляем самый широкий текст у кнопок #См. выше #Размещаем кнопки     set BDwidth_canvas 0      set maxTextHeightPx [font metrics fontTEMP_drawer -linespace]       set maxTextHeightPx [expr {$maxTextHeightPx + ( $maxTextHeightPx / 2)}]      set drawerHeightPx $maxTextHeightPx      set xLocTextPx [expr {($::::scrwidth - $drawerWidthPx) / 2}]      set yLocTextPx [expr $BDwidth_canvas + ($drawerHeightPx / 2) + $boxbut]     set canvasHeightPx [expr $Ndrawers * $drawerHeightPx]      set drawerCNT 0     set Ndrawers [expr {[array size but] - 1}]     while { $drawerCNT <= $Ndrawers } {       set yLineLocPx [ expr (( $drawerCNT ) * $drawerHeightPx + $boxbut)] #Линия перед текстом       $fr.can create line \          $xLocTextPx $yLineLocPx \          [expr $drawerWidthPx + $xLocTextPx] $yLineLocPx \          -fill "#a0a0a0" -width $widl        $fr.can create text [expr $xLocTextPx + 5] $yLocTextPx \ 	-anchor w \         -font fontTEMP_drawer \         -text "$but($drawerCNT)" \         -tag textlineTag($drawerCNT)        if {$drawerCNT == 0} { 	    if {$fr == ".fr1"} { 		$fr.can bind textlineTag($drawerCNT)  <ButtonRelease-1>   {butImg "but1"} 	    } else { 		$fr.can bind textlineTag($drawerCNT)  <ButtonRelease-1>   {butReturn} 	    } 	} else { 	    frame .fn$drawerCNT -background white -relief flat -pady 0 -padx 0 	    set titul $but($drawerCNT) 	    set cmd "$fr.can bind textlineTag($drawerCNT)  <ButtonRelease-1>   {butCliked $drawerCNT .fn$drawerCNT}" 	    set cmd [subst "$cmd"] 	    eval $cmd  	    set but1(0) "Возврат в основное меню" 	    page_func ".fn$drawerCNT" voda "$titul" "but1" 	}  	incr drawerCNT  	set yLocTextPx [ expr $yLocTextPx + $drawerHeightPx] #Завершаюшая линия 	if { $drawerCNT > $Ndrawers } {     	    set yLineLocPx [ expr (( $drawerCNT ) * $drawerHeightPx + $boxbut)]     	    $fr.can create line $xLocTextPx $yLineLocPx \             [expr $drawerWidthPx + $xLocTextPx] $yLineLocPx \             -fill "#a0a0a0" -width $widl 	}     } } #Собственно скрипт #Считываем размеры экрана set ::scrwidth [winfo screenwidth .] set ::scrheight [winfo screenheight .] set ::typetlf 0 #Проверяем, что это телефон if {$::scrwidth < $::scrheight} {     set ::typetlf 1 } set ::padls 20 set ::padlx 15 set ::padly 15 if {$::typetlf} { 	wm attributes . -fullscreen 1 #Логотип продуктв 	scaleImage logo_product 2 	set ::padls 50 	set ::padlx 75 	set ::padly 50 } else { #Конфигурирование виджета под смартфон     set ::scrwidth 370     set ::scrheight 700     wm minsize . $::scrwidth $::scrheight     set geometr $::scrwidth     append geometr "x"     append geometr $::scrheight     append geometr "+0+0"     wm geometry . $geometr }  #Грузим картинки image create photo voda -file [file join $mydir "imageme" "voda_400x800.png"] #Логотип продукта image create photo logo_product -file [file join $mydir "imageme" "validcertkey_51x24.png"]  #Создаем название продукта set name_product "CryptoArmPKCS-A"  label .labtitul -image logo_product -compound left -fg snow -text $name_product -font {Arial 10 bold} -anchor w  -width [winfo screenwidth .] -pady $::padls -padx 10 -bg #222222  pack .labtitul -anchor nw -expand 0 -fill x -side top  -padx 1 -pady 0 #Создаем страницы с функционалом set i 1 ttk::frame .fr$i -pad 0 -padding 0 #Кнопки основного меню set but(0) "Стартовая страница"  set but(1) "Подписать документ" set but(2) "Работаем с ЭП (PKCS7)"  set but(3) "Запрос на сертификат"  set but(4) "Просмотр запроса/сертификата"  set but(5) "Список криптомеханизмов" set but(6) "Просмотр ASN1-структуры"  set but(7) "Объекты токена"  set but(8) "Работаем с PKCS12/PFX"  set but(9) "Самоподписанный сертификат" set but(10) "Об Утилите/Дистрибутивы"  set but(11) "Создать Токены" set but(12) "Конфигурировние токена" if {$::typetlf} {     scaleImage voda 3 2 } #Создаем страницу с функционалом page_func ".fr$i" voda "Функционал" "but" #Отображаем страницу с функционалом pack .fr$i -side top -anchor center -expand 1 -fill both -side top  -padx 0 -pady 0 

Этот скрипт также готовит болванки для каждой функциональной кнопки:

Наполнение болванок ведется классическими и тематическими виджетами (labelframe, button и т.д.). Одну из таких заполненных балванок можно видеть на первом скриншоте справа. Посколько на первом этапе мы сосредоточились на работе с контейнером PKCS#12, то код практически без изменений был использован и для cryptoarmpkcs-A. На данном этапе реализованы следующие функции:
— подписать документ (Cades-BES, CAdes-T, CAdes-XLT1);
— работаем с ЭП (PKCS7), включая добавление подписанта;
— просмотр сертификатов/запросов на сертификаты:

— работаем с PKCS12/PFX;
— об утилите/Дистрибутивы:

Остальные функции больше связаны с токенами PKCS#11. Их портирование отложено на Новый Год. Планируется подключение программного токена и подключение к облаку токенов.
С точки зрения функционала практически все аналочично утилите cryptoarmpkcs. Но есть отдельные отличия. Например, после подписания документа, утилита спрашивает будет ли подпись проверяться на сайте Госуслуг:

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

Следует иметь ввиду, что проверка как подписи, так и сертификата на сайте Госуслуг имеет смыл только если сертификат был получен в аккредитованном удостоверяющем центре (УЦ). В противном случае подпись всегда будет недействительной.
Для вызова браузера пришлось добавить пару строк в процедуру openUrl:

proc openURL {url} {   global typesys   global macos   #  global windowsOS #проверка, что утилита выполняется на смартфоне Android   if {$::typetlf} { #Запуск браузера  	borg activity android.intent.action.VIEW $url text/html  	return   } . . . }

Браузер на Android вызывается следующим образом:

borg activity android.intent.action.VIEW <URL> text/html 

Небольшая особенность есть при добавлении нового подписанта к ранее подписанному документу. Сертификат нового подписанта (вернее даже контейнер PKCS#12 с сертификатом и закрытым ключом) должен быть заранее выбран на странице «Подписать документ» или «Работаем с PKCS12/PFX», о чем утилита напомнит:

При длительных операциях как и прежде будут идти часы:

Осталось сказать откуда скачать дистрибутивы и Поздравить с наступающим Новым Годом и пожелать всем всего наилучшего в 2020 Году!
imageИтак, дистрибутивы для Linux, OS X, Windows и Android:


ссылка на оригинал статьи https://habr.com/ru/post/482574/

Iteraptor: библиотека для глубокого прозрачного мап-редьюса

Структуры данных Elixir — иммутабельны. Это здорово с точки зрения уверенности в том, что наши данные не будут искорежены до неузнаваемости в каком-то другом нерелевантном куске кода, но это немного раздражает, когда нам нужно изменить глубоко вложенную структуру.

У нас есть блестящая абстракция Access, которая донельзя упрощает четыре основные операции на глубоко вложенных объектах, при помощи экспортируемых по умолчанию из Kernel функций:

Эти четыре мушкетера (и д’Артаньян Kernel.get_and_update_in/{2,3} обычно используются как-то так:

iex> users = %{"john" => %{age: 27, mood: ""}, "meg" => %{age: 23}}  # получить значение iex> get_in(users, ["john", :age]) #⇒ 27  # записать значение iex> put_in(users, ["john", :age], 28) #⇒ %{"john" => %{age: 28, mood: ""}, "meg" => %{age: 23}}  # обновить значение iex> update_in(users, ["john", :mood], & &1 + 1) #⇒ %{"john" => %{age: 28, mood: ""}, "meg" => %{age: 23}}  # удалить значение iex> pop_in(users, ["john", :mood]) #⇒ {"", %{"john" => %{age: 27}, "meg" => %{age: 23}}}

Это удобно, и работает во многих случаях… до тех пор, пока оно не работает. Чтобы использовать Access, необходимо знать путь к целевому элементу, и это все требует значительного количества шаблонного кода, чтобы разом обновить несколько вложенных значений (например, удалить все листья со значением nil, или зазвездить содержимое всех полей, которые не нужно показывать в логах).

Чтобы сделать оптовую работу со вложенными структурами, и была создана библиотека Iteraptor.

TL;DR:


Итерирование всего, что можно итерировать в Elixir волшебно. Чтобы сделать что-нибудь итерируемым, достаточно имплементировать протокол Enumerable для этого конкретного типа. Можно группировать проходы в пайплайны, маппить, редьюсить, фильтровать, прореживать… Простите мой французский, да. Каждый, кто провел с Elixir хотя бы восемь часов, определенно видел (и даже, возможно, написал) что-то вроде этого:

~w|саня ваня оля вера| |> Enum.map(&String.capitalize/1) |> Enum.each(fn capitalized_name ->      IO.puts "Hello, #{capitalized_name}!"    end)  # Hello, Саня! # Hello, Ваня! # Hello, Оля! # Hello, Вера!

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

Количество вопросов на Stack Overflow с вопросом «как я могу поменять вложенную структуру?» заставило меня, наконец, создать эту библиотеку. Реализация в Elixir выглядит немного запутанной, поскольку все вокруг — иммутабельно, и нельзя просто спустиьтся вниз по веткам структуры вплоть до листьев, изменяя все необходимое на месте. Потребуется аккумулятор, как, впрочем, под любым капотом функционального кода. Изменение вложенных структур — вероятно, единственный пример, который я встречал в своей жизни, когда мутабельность делает вещи проще.

В качестве бонуса к обычным мапредьюсам, я добавил реализацию для сохранения значения глубоко внутри структуры, которая создает промежуточные ключи по мере необходимости. Она ведет себя как предложенная, но отвергнутая в ruby core Hash#bury. Еще эта библиотека умеет «джейсонифицировать» вложенные структуры, содержащие keywords, которые не могут быть просто так сериализованы в json, поскольку внутри представлены как списки двухэлементных кортежей ([foo: :bar] == [{:foo, :bar}]), а кортежи не сериализуемы просто так из коробки.


Итак, поприветствуем библиотеку, которая в хвост и в гриву итерирует любой map / keyword / list почти так же просто, как и стандартные Enum.each/2 и Enum.map/2.

Возможности

  • Iteraptor.each/3 простая итерация, возвращает саму структуру;
  • Iteraptor.map/3 маппинг, возвращает замапленную структуру;
  • Iteraptor.reduce/4 редьюс, возвращает аккумулятор;
  • Iteraptor.map_reduce/4 мап и редьюс, возвращает кортеж с результатом маппинга и аккумулятором;
  • Iteraptor.filter/3 фильтрует структуру при помощи функции, полученной последним параметром;
  • Iteraptor.jsonify/2 подготавливает структуру для сериализации в json: все keywords заменяются на maps, ключи конвертируются в строки;
  • Iteraptor.Extras.bury/4 записывает значение вглубь структуры, создавая промежуточные ключи по мере необходимости;
  • Iteraptor.to_flatmap/2 превращает структуру в плоскую, заботливо создавая индексы для списков; оригинальную структуру можно получить обратно с использованием
  • Iteraptor.from_flatmap/3 превращает плоскуб структуру с конкатенированными ключами в глубоко вложенную;
  • use Iteraptor.Iteraptable автоматическая имплементация протоколов Enumerable и Collectable, а также Access behaviour для структуры. Есть более подробное описание того, что там под капотом (англ.).

Слова ничего не стоят, покажи код!

Итерирование, маппинг, редьюсинг

# each iex> %{a: %{b: %{c: 42}}} |> Iteraptor.each(&IO.inspect(&1, label: "each"), yield: :all) # each: {[:a], %{b: %{c: 42}}} # each: {[:a, :b], %{c: 42}} # each: {[:a, :b, :c], 42} %{a: %{b: %{c: 42}}}  # map iex> %{a: %{b: %{c: 42}}} |> Iteraptor.map(fn {k, _} -> Enum.join(k) end) %{a: %{b: %{c: "abc"}}}  iex> %{a: %{b: %{c: 42}, d: "some"}} ...> |> Iteraptor.map(fn ...>      {[_], _} = self -> self ...>      {[_, _], _} -> "********" ...>    end, yield: :all) %{a: %{b: "********", d: "some"}}  # reduce iex> %{a: %{b: %{c: 42}}} ...> |> Iteraptor.reduce([], fn {k, _}, acc -> ...>      [Enum.join(k, "_") | acc] ...>    end, yield: :all) ...> |> :lists.reverse() ["a", "a_b", "a_b_c"]  # map-reduce iex> %{a: %{b: %{c: 42}}} ...> |> Iteraptor.map_reduce([], fn ...>      {k, %{} = v}, acc -> {​{k, v}, [Enum.join(k, ".") | acc]} ...>      {k, v}, acc -> {​{k, v * 2}, [Enum.join(k, ".") <> "=" | acc]} ...>    end, yield: :all) {​%{a: %{b: %{c: 42}}}, ["a.b.c=", "a.b", "a"]}  # filter iex> %{a: %{b: 42, e: %{f: 3.14, c: 42}, d: %{c: 42}}, c: 42, d: 3.14} ...> |> Iteraptor.filter(fn {key, _} -> :c in key end, yield: :none) %{a: %{e: %{c: 42}, d: %{c: 42}}, c: 42}

Глубокая вложенность → плоская структура и обратно

iex> %{a: %{b: %{c: 42, d: [nil, 42]}, e: [:f, 42]}} ...> |> Iteraptor.to_flatmap(delimiter: "_") #⇒ %{"a_b_c" => 42, "a_b_d_0" => nil, "a_b_d_1" => 42, "a_e_0" => :f, "a_e_1" => 42}  iex> %{"a.b.c": 42, "a.b.d.0": nil, "a.b.d.1": 42, "a.e.0": :f, "a.e.1": 42} ...> |> Iteraptor.from_flatmap #⇒ %{a: %{b: %{c: 42, d: [nil, 42]}, e: [:f, 42]}}

Плюшки

iex> Iteraptor.jsonify([foo: [bar: [baz: :zoo], boo: 42]], values: true) %{"foo" => %{"bar" => %{"baz" => "zoo"}, "boo" => 42}}  iex> Iteraptor.Extras.bury([foo: :bar], ~w|a b c d|a, 42) [a: [b: [c: [d: 42]]], foo: :bar]


Исходники открыты, документация довольно подробная, у нас в продакшене уже почти два года.

Удачных итераций!


ссылка на оригинал статьи https://habr.com/ru/post/482478/

Космические штрихи уходящего года

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


Сборка РН «Союз», фото Роскосмоса

На фоне подсчетов количества запусков, как мне кажется, был недостаточно замечен факт того, что впервые за 16 лет у Роскосмоса не произошло ни одного аварийного пуска. В последний раз такое было аж в 2003. И ведь не сказать, что обошлось совсем без происшествий.

21 февраля с Байконура стартовала ракета-носитель «Союз-2.1б» со спутником EgyptSat-A. Спутник был выведен успешно, но спустя несколько дней выяснилось, что примерно за шесть секунд до отделения разгонного блока «Фрегат» от третьей ступени на ней аварийно выключился двигатель. Связка разгонного блока и спутника находилась на суборбитальной траектории с перицентром на 57 км ниже расчетного. К счастью, система управления «Фрегата» посчитала, что сможет вывести спутник несмотря на более низкую начальную орбиту, и благополучно справилась с задачей. Расследование показало, что причиной выключения двигателя стала ошибка в документации — перепутали объемы топлива и окислителя, одного заправили больше, чем нужно, второго — меньше.

Борьба за качество — штука нелегкая и заметная обычно только по неудачам, так что важность первого за долгое время безаварийного года стоит всячески подчеркивать. И, конечно же, смотреть за тем, что будет дальше — был ли 2019 год удачной случайностью, или же можно надеяться на тенденцию.

По своеобразной иронии судьбы в этом же году произошла авария с потерей полезной нагрузки у Европейского космического агентства, у которого, наоборот, серьезная авария происходит раз в несколько лет. Ракета-носитель «Вега» с разведывательным спутником ОАЭ была потеряна на второй минуте полета практически сразу после включения второй ступени.


Кадр из трансляции

Видимые по трансляции исчезновение пламени выхлопа и переход траектории на баллистическую предполагали полный отказ твердотопливного двигателя второй ступени. Расследование установило, что после 14 секунд работы вторая ступень разрушилась в верхней части, разорвав ракету пополам. Это был пятнадцатый пуск ракеты-носителя «Вега» и ее первая авария.

Вообще, уходящий год неоднократно и наглядно показал сложность космонавтики — падений и взрывов хватало с лихвой. Вроде бы уже простая и близкая Луна «показала зубы» — после успешной посадки на обратную сторону китайского зонда о наш спутник разбились израильский и индийский. А ведь всего несколько лет назад Индия стала первой страной, успешно доставившей аппарат на орбиту Марса с первой попытки.


След от падения «Берешита»

Израильский зонд «Берешит», первоначально показавший себя блестяще в сложном и длительном перелете, не смог справиться с основной задачей посадки. Отказ гироскопа и отправленная на зонд команда запустили цепочку событий, в результате которой аппарат в самое ответственное время торможения падал с выключенным двигателем и в итоге разбился. Индийский аппарат Vikram успешно снизился до высоты 7,4 км и скорости 146 м/с, но вот затем двигатели гасили скорость быстрее, чем требовалось, и к этапу точного торможения и посадки зонд подошел с полетными параметрами, исходя из которых система управления не смогла обеспечить мягкую посадку. Трансляция показала отклонение от плановой траектории, затем кувыркающуюся модель зонда на фоне уменьшающейся высоты, и потом связь пропала. Аппарат разбился о Луну.

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

А в ноябре при испытаниях прототипа Starship он красиво лопнул, не выдержав давления.

Совсем недавно только частично успешно завершился беспилотный испытательный полет корабля Starliner от Boeing — из-за неверной установки таймера корабль растратил топливо для стыковки с МКС и был вынужден вернуться на Землю раньше срока. Вообще стоит отметить, что в 2019 не полетели не только астронавты на Crew Dragon и Starliner, но и не добрались до космоса туристы на суборбитальных New Shepard и SpaceShipTwo — у обоих кораблей сроки все продолжают сдвигаться вправо. Правда, Virgin Galactic в феврале прокатила туристку до почти 90 км, сделав ее астронавткой по классификации США (выше 50 миль/80 км), но линии Кармана в 100 км они не достигли, и больше полетов не было.

А вот Ирану, наверное, не повезло сильнее всего — два пуска в 2019 были неудачны, а в августе третья ракета взорвалась прямо на стартовой площадке.


Фото Maxar Technologies

Окончания, начала, возобновления и продолжения

В начале 2019 года была официально признана завершенной миссия марсианского ровера Opportunity — он так и не вышел на связь, выключившись во время рекордной пылевой бури на Марсе в 2018. За пятнадцать земных лет аппарат проехал 45 с небольшим километров, почти в 60 раз превысив расчетный срок службы. Но уже скоро на Марс составить компанию оставшемуся одному Curiosity должны отправиться аж три ровера: американский, китайский и европейский с российской посадочной платформой.

Превысив почти в три раза гарантийный срок закончилась миссия российского аппарата «Спектр-Р». Уникальный телескоп, в тысячу раз более зоркий, чем «Хаббл» (ах, если бы в его диапазоне изображения выглядели так же прекрасно!), к счастью, почти дождался смены — в этом же году на орбиту отправился второй телескоп из программы «Спектр» — «Спектр-РГ».

Транснептуновый объект (486958) 2014 MU69, мимо которого в январе пролетел аппарат «Новые горизонты», получил официальное название. Вместо «Ультима Туле» (легендарного острова на севере Европы) его назвали Аррокот («небо» на языке индейцев поухатан). У «Новых горизонтов» пока нет определенной новой цели, но она вполне может появиться.

И совсем недавно, на прошлой неделе, вернулась к полетам китайская тяжелая ракета-носитель «Великий поход-5», от которой зависит множество космических программ — лунный «Чанъэ-5», марсианский аппарат и модули новой орбитальной станции. В 2017 году второй пуск закончился неудачно — из-за разрушения турбонасоса ракета и полезная нагрузка были потеряны.

Ну и закончить мне хочется напоминанием про, на мой взгляд, самую экстравагантную миссию года — японский зонд «Хаябуса-2» стрелял по астероиду и бомбил его. В ноябре миссия аппарата у астероида Рюгу закончилась, и после теста двигателя в начале декабря он возвращается домой.


ссылка на оригинал статьи https://habr.com/ru/post/482480/