Небольшой пример из реальной жизни; изучение репозиториев GitHub
План
Мы будем использовать HttpKit для доступа к GitHub API и получения информации о репозиториях организации. Затем мы будем использовать встроенные функции Clojure, чтобы управлять коллекцией и исследовать ее. На данный момент мы не будем пытаться делать что-либо асинхронное или сложное - мы можем оставить это на будущее :)
Начиная
В каком-то новом каталоге я создал deps.edn
файл со следующими зависимостями:
Затем мы можем запустить clj
REPL в том же каталоге, что и файл deps.edn
:
❯ clj Clojure 1.10.1 user=>
И требовать (вставить) наши зависимости в REPL:
Быстрое напоминание о карте
Ключи на картах также являются функциями, что позволяет нам очень кратко получать доступ к парам "ключ-значение" с карты. Скажем, мы определяем карту с именем test-map
со свойствами :error
и :status
. Мы могли бы получить доступ к свойству :status
следующим образом:
Сделать запрос API
👋 Для использования API проще всего сгенерировать токен личного доступа.
На этом этапе лучше всего попытаться получить доступ к GitHub API, чтобы убедиться, что наша аутентификация и URL-адреса верны. Отрегулируйте def's
ниже на свою информацию / настройки и вставьте их в REPL:
Обещания
Функция HttpKit (http/get)
возвращает обещание. Это обещание будет преобразовано в карту после завершения. Мы можем заблокировать, пока это обещание не будет выполнено, используя символ @: @(http/get .. ..)
После завершения мы можем получить доступ к различным парам "ключ-значение", например :status
, которое должно быть 200:
Заголовки
У GitHub есть хорошая модель разбивки на страницы, которую мы будем использовать, чтобы сообщить нам, нужно ли нам делать больше запросов. Если мы посмотрим на заголовки ответов, то обнаружим свойство link
, которое содержит разделенный запятыми массив ссылок и их относительное положение относительно текущей страницы. Мы можем получить к нему доступ из карты ответов с помощью функции get-in
. Затем мы можем определить, есть ли у нас следующая страница для загрузки, если строка ссылки содержит rel="next"
:
Теперь мы можем создать get-repos
функцию, чтобы собрать все вышеперечисленное вместе. Принимает два параметра; page
и per_page
- номер текущей страницы и количество возвращаемых репозиториев на страницу, соответственно:
⚠️ На данный момент максимальное значение
per_page
для API GitHub составляет 100.
Обратите внимание, что наша функция get-repos
возвращает карту, содержащую ответ :body
в виде проанализированного массива json и :has_next
в виде логического.
Цикл по страницам GitHub
У нас есть вся небольшая логика для выполнения запросов и получения репозиториев GitHub. Затем нам нужно перебрать все страницы данных и добавить репозитории в постоянно растущий список.
Напоминание о быстром цикле
Циклы в Clojure - это либо круто, либо катастрофа, в зависимости от вашего опыта. Итак, начнем с простого цикла. Здесь мы привязываем i
к 0
и вызываем recur
, только если i < 5
. Когда мы вызываем recur
, мы привязываем i
к значению i + 1
, таким образом увеличивая i
и выводим его значение:
Значение приведенного выше выражения является последним возвращенным значением функции loop
. В данном случае 5
. Если мы установим это выражение на def
, мы установим значение этого def
на 5
:
Цикл по страницам
Мы можем быстро проверить себя, чтобы убедиться, что у нас больше страниц, после запроса только одного репозитория.
👋 Убедитесь, что у вашей организации более одного репозитория! 😅
Обратите внимание на несколько привязок let ниже. Сначала resp
устанавливается на результат выражения (get-repos 1 1)
, а затем has_next
будет иметь доступ к вновь установленной переменной resp
, все в той же строке кода:
Мы можем объединить вышеизложенное с некоторой логикой цикла, чтобы получить все репозитории. Я помещаю все репозитории в глобальный список под названием repos
:
Я знаю, что в моей организации более 200 репозиториев, поэтому я установил для переменной
per_page
максимальное значение 100.
Выполните быструю проверку, чтобы убедиться, что количество репозиториев соответствует ожидаемому:
(println (count @repos)) ; => 233
Здорово! Все наши репозитории находятся внутри глобального атомарного списка. Теперь мы можем разрезать эти данные по своему усмотрению.
Фильтрация
Давайте попробуем и посмотрим, сколько репозиториев активно, I.E. не archived
. Мы можем добиться этого, используя Clojure filter
вместе с анонимной функцией. Анонимная функция принимает карту репозитория в качестве входных данных и проверяет, является ли свойство :archived
ложным:
И вот оно! active-repos
содержит репозитории со свойством :archived
, равным false
.
Я надеюсь, что это был полезный способ познакомиться с Clojure! Мы используем библиотеку для выполнения HTTP-запросов, а затем базовые языковые функции для выполнения некоторых базовых циклов, фильтрации, множественных запросов и управления состоянием.