Русский
Русский
English
Статистика
Реклама

Jsqry лучше, чем jq

В своей прошлой статье на Хабре я писал про библиотеку Jsqry, которая предоставляет простой и удобный язык запросов (DSL) к объектам JSON. С тех пор прошло много времени и библиотека тоже получила свое развитие. Отдельный повод для гордости библиотека имеет 98% покрытие кода тестами. Однако в этой статье речь не совсем о ней.


Думаю, многие из вас знакомы с инструментом jq, который является практически стандартом де-факто для работы с JSON в командной строке и скриптах. Я тоже являлся её активным пользователем. Но меня все время беспокоила неоправданная сложность и неинтуитивность синтаксиса запросов этой утилиты. И не меня одного, вот лишь несколько цитат с hacker news:


I have been using jq for years and still can't get it to work quite how I would expect it to.

I have the same issue with jq. I need to use my google fu to figure out how to do anything more than a simple select.

I don't know what the term would be, mental model, but I just can't get jq to click. Mostly because i only need it every once in a while. It's frustrating for me because it seems quite powerful.

I know I might be a dissenting opinion here, but I can never wrap my head around jq. I can manage jq ., jq .foo and jq -r, but beyond that, the DSL is just opaque to me.

Let's just say it: jq is an amazing tool, but the DSL is just bad.

Yeah, I find jq similar to writing regexes: I always have to look up the syntax, only get it working after some confusion why my patterns aren't matching, then forget it all in a few days so have to relearn it again later.

Одним словом, вы уже наверное догадались. Пришла идея, а почему бы не обратить мою JS библиотеку в исполняемый файл для командной строки. Здесь есть один нюанс. Библиотека написана на JS и её DSL также опирается на JS. Это значит, что надо найти способ упаковать программу и какой-нибудь JS-runtime в самодостаточный исполняемый файл.


jsqry GraalVM edition


Для тех кто еще не в теме (неужели еще есть такие? oO) напомню, что GraalVM это такая прокачанная JVM от Oracle с дополнительными возможностями, самые заметные из которых:


  1. Полиглотная JVM возможность бесшовного совместного запуска Java, Javascript, Python, Ruby, R, и т.д. кода
  2. Поддержка AOT-компиляции компиляция Java прямо в нативный бинарник
  3. Улучшения в JIT-компиляторе Java.

Освежить представление о Graal можно, например, в этой хабра-статье.


Теоретически, объединение возможностей пунктов 1. и 2. должно решить поставленную задачу обратить код на JS в исполняемый файл.


Так родился проект https://github.com/jsqry/jsqry-cli. Правда, не спешите добавлять в закладки в данный момент проект deprecated. Идея оказалась рабочей, но непрактичной. Дело в том, что размер исполняемого файла получался 99 Мб. Как-то не очень хорошо для простой утилиты командной строки. Тем более, если сравнить с jq с её размером 3.7 Мб для последней версии для Linux 64.


В идеале хотелось бы получить размер не больше мегабайта.


Тем не менее, решил оставить этот репозиторий как практический пример того как собрать из Java + JS кода исполняемый файл при помощи GraalVM.


Небольшой обзор этого решения


Решение имеет основной код запускаемого приложения в единственном файле App.java. Этот код выполняет обработку параметров командной строки, используя стандартную java-библиотеку Apache Commons CLI.


Далее java-код вызывает код на javascript из файлов, находящихся в директории ресурсов src/main/resources.


При этом интересный момент. Вроде-бы простейший код для вычитывания содержимого файла из ресурса


scripts.add(new String(Files.readAllBytes(Paths.get(jsFileResource.toURI()))));

под Граалем (то есть, будучи скомпилированным через native-image) падало с


java.nio.file.FileSystemNotFoundException: Provider "resource" not installed

Выручил древний "хак" для чтения строки из InputStream


scripts.add(new Scanner(jsFileResource.openStream()).useDelimiter("\\A").next());

Короче говоря, надеяться на 100% поддержку всех функций стандартной Java Граалем все еще не приходится.


Недавно аналогичной неприятной находкой оказалось отсутствие поддержки java.awt.Graphics. Это помешало использовать GraalVM для реализации AWS Lambda для конвертации картинок.


jsqry QuickJS edition


Где-то в это же время я узнал о новом компактном движке JS QuickJS от гениального французского программиста Фабриса Беллара. В своем составе этот инструмент несёт компилятор qjsc джаваскрипта в исполняемый файл. Также поддерживается почти полная совместимость с ES2020. То что нужно!


Таким образом, появилась вторая инкарнация CLI-версии jsqry: https://github.com/jsqry/jsqry-cli2.
Этот подход оказался более жизнеспособным и уже принес несколько релизов.


Итак, что же такое jsqry?


jsqry это маленькая утилита командной строки (похожая на jq) для выполнения запросов к JSON используя "человеческий" DSL.


Цель этой разработки представить функционал JS библиотеки jsqry в форме интерфейса командной строки.


Примеры использования


запрос


$ echo '[{"name":"John","age":30},         {"name":"Alice","age":25},         {"name":"Bob","age":50}]' | jsqry 'name'[  "John",  "Alice",  "Bob"]

первый элемент


$ echo '[{"name":"John","age":30},         {"name":"Alice","age":25},         {"name":"Bob","age":50}]' | jsqry -1 'name'"John"

использование параметризации запроса


$ echo '[{"name":"John","age":30},{"name":"Alice","age":25},{"name":"Bob","age":50}]' \    | jsqry '[ _.age>=? && _.name.toLowerCase().startsWith(?) ]' --arg 30 --arg-str joh [  {    "name": "John",    "age": 30  }]

использование в роли простого JSON pretty-printer


$ echo '[{"name":"John","age":30},{"name":"Alice","age":25},{"name":"Bob","age":50}]' \ | jsqry[  {    "name": "John",    "age": 30  },  {    "name": "Alice",    "age": 25  },  {    "name": "Bob",    "age": 50  }]

Выходной JSON утилиты по умолчанию отформатирован. И раскрашен!


что-то более хитрое


Отфильтровать элементы больше 2, добавить к каждому 100, отсортировать по убыванию и взять 2 последних элемента. Комбинируя эти возможности вы можете строить сколь угодно сложные запросы. Узнать больше о поддерживаемом DSL.


$ echo '[1,2,3,4,5]' | jsqry '[_>2] {_+100} s(-_) [-2:]'[  104,  103]

полная мощь JS


Поскольку jsqry вмещает полноценный JS-движок в исполняемом файле менее 1 Мб, полная мощь JS в ваших руках!


$ echo '["HTTP://EXAMPLE.COM/123",          "https://www.Google.com/search?q=test",          "https://www.YouTube.com/watch?v=_OBlgSz8sSM"]' \ | jsqry '{ _.match(/:\/\/([^\/]+)\//)[1].toLowerCase() }'[  "example.com",  "www.google.com",  "www.youtube.com"]

help-сообщение


$ jsqryjsqry ver. 0.1.2Usage: echo $JSON | jsqry 'query' -1,--first     return first result element -h,--help      print help and exit -v,--version   print version and exit -c,--compact   compact output (no pretty-print) -u,--unquote   unquote output string(s) -as ARG, --arg-str ARG  supply string query argument -a ARG, --arg ARG      supply query argument of any other type

Небольшое сравнение с jq


А здесь я подготовил небольшое практическое сравнение jq и jsqry на примерах.


Установка


Текущая версия (на момент написания): 0.1.2.


К сожалению, только Linux x64 поддерживается в данный момент. Надеюсь, поддержка других платформ будет скоро добавлена. Буду рад здесь вашей помощи.


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


$ sudo bash -e -c "wget https://github.com/jsqry/jsqry-cli2/releases/download/v0.1.2/jsqry-linux-amd64 -O/usr/local/bin/jsqrychmod +x /usr/local/bin/jsqryecho \"jsqry \$(jsqry -v) installed successfully\" "

О тестировании CLI-утилиты


При разработке утилиты на GitHub хотелось реализовать какое-то подобие автоматического тестирования. Юнит-тесты довольно просто писать, когда вы работаете на уровне языка программирования. Интереснее дело обстоит, если хочется протестировать CLI-утилиту как единое целое, как черный ящик. Благо, в нашем случае это должно быть просто и логично, поскольку утилита представляет собой то, что функциональщики бы назвали чистой функцией выход определяется исключительно входом.


Попытав Гугл запросами вида "bash unit testing" и отметя варианты BATS, ShellSpec, Bach и несколько других подходов, как чересчур тяжеловесные для моего случая, а также самописную систему тестирования (картинка про 14 стандартов), остановился на решении tush, гениальном в своей простоте.


Тесты на tush представляют собой текстовый файл в таком синтаксисе


$ command --that --should --execute correctly| expected stdout output$ command --that --will --cause error@ expected stderr output? expected-exit-code

Причем tush разбирает только строки начинающиеся на $, |, @ и ? все остальные могут быть любым текстом, например описанием соответствующих тестов. При запуске теста инструмент запускает все строки, начинающиеся на $ и просто сравнивает реальный вывод с ожидаемым, используя обычный diff. В случае отличия тест заканчивается неудачей, а diff отличия выводится пользователю. Пример:


$ /bin/bash /home/xonix/proj/jsqry-cli2/tests.sh--- tests.tush expected+++ tests.tush actual@@ -1,5 +1,5 @@ $ jsqry -v-| 0.1.2+| 0.1.1 $ jsqry -h | jsqry ver. 0.1.1!!! TESTS FAILED !!!

Таким образом удалось покрыть тестами базовые сценарии работы с утилитой в виде одного файла
tests.tush.


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


Удалось этот тестовый сценарий реализовать в виде GitHub Action, который запускается на каждый коммит, гарантируя корректность каждого изменения и предоставляя замечательный бейдж:


Build and test


Другие особенности решения


Раскрашивание JSON


Добавить раскраску выходного JSON оказалось на удивление просто. Решение основано на подходе из проекта zvakanaka/color-json с немного оптимизированными цветами, которые были подобраны на основе прекраснейшего StackOverflow комментария. Для примера привожу сравнение раскраски с jq. В моей версии строки более яркие, а null имеет красный цвет для пущей заметности.


screenshot


Понятно, что раскрашивание будет выполняться только при работе с терминалом.


Подключение npm-версии библиотеки в QuickJS


Опишу немного процесс по которому npm-версия библиотеки jsqry подтягивается и включается в результирующий исполняемый файл. Для этого присутствует стандартный package.json с необходимой версией библиотеки. Библиотека вытягивается стандартным npm i. Затем используется небольшой скрипт prepare-for-qjs.py, роль которого состоит в замене экспортирования в стиле nodejs на экспортирование в стиле модулей ES, только последнее поддерживается движком QuickJS. Далее уже полученный файл импортируется в основной код утилиты jsqry-cli.js.


Чтение входной строки в UTF-8 в QuickJS


В случае QuickJS некоторой мороки стоит считывание строки из stdin. Дело в том, что минималистичная стандартная библиотека, доступная в QuickJS, реализует только считывание байтов. Поэтому понадобился некоторый ручной код, чтоб перегнать байтики UTF-8 в JS-строку. К счастью, его не пришлось изобретать, а удалось позаимствовать из другого проекта на QuickJS: twardoch/svgop.


Сборка утилиты


Сборка утилиты выполняется скриптом build.sh.


Перечислю несколько "фишек" этого скрипта, которые оказались весьма полезными.


Первое скрипт безусловно вызывает в самом конце скрипт тестов tests.sh. Это гарантирует, что каждая вновь собранная версия утилиты будет протестирована, а сборка развалится если тесты будут неудачны.


Второе скрипт build.sh автоматически скачивает и компилирует заданную версию QuickJS, а скрипт tests.sh выполняет то же для инструмента тестирования tush. Очень удобно можно мгновенно продолжить разработку проекта на другой машине без лишних телодвижений.


Третий момент. В самом конце сборки выполняется команда ls -lh jsqry чтоб показать размер результирующего файла. Как я уже упомянул, я немного параноидален на этот счет, хочется чтоб CLI-утилита имела наименьший размер. Я рад, что это принесло полезный побочный результат помогло устранить регрессию, выявленную этой проверкой в одном из прошлых релизов QuickJS.


В данный момент размер исполняемого файла составляет 652 KB. Мне кажется, довольно неплохо, учитывая что файл включает в себя полноценный движок современного стандарта JS.


В качестве послесловия


Прошу заинтересовавшихся попробовать представленную утилиту как замену jq. Также буду рад услышать ваши пожелания по доработке и конструктивную критику.

Источник: habr.com
К списку статей
Опубликовано: 30.10.2020 16:06:21
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Javascript

*nix

Node.js

Quickjs

Query

Json

Cli

Jq

Ecmascript

Nodejs

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru