Небольшой пример из реальной жизни; изучение репозиториев 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-запросов, а затем базовые языковые функции для выполнения некоторых базовых циклов, фильтрации, множественных запросов и управления состоянием.