Category: it

Category was added automatically. Read all entries about "it".

jQuery. Последовательное изменение данных в обработчиках.

Неожиданно... Оказывается обработчики связанных с событием происходящим в одном объекте - последовательно изменяют данных события. Например:

// Какой-то наш jQuery-объект (вообще любой, необязательно связанный с DOM-элементом).
var $obj = $({});

// Устанавливаем обработчик, который выведет данные, а затем их изменит
$obj.on('myev', function(jqEv, data){
  console.log(data);
  data.key += '-extra';
  data.approved = true;
});
// Устанавливаем обработчик, который просто выводит данные обрабатываемого события
$obj.on('myev', function(jqEv, data){
  console.log(data);
});

// Генерируем событие с некоторыми данными
$obj.trigger('myev', {key:'val'});

Вывод:
-----------------------------
Object {key: "val"}
Object {key: "val-extra", approved: true}

В итоге на заметку - обработчиками можно последовательно изменять данных связанные с событием объекта.

Рейтинг библиотек в Quicklisp репозитории

  Вообще говоря в движении opensource есть свои плюсы и минусы. Плюсов разумеется гораздо больше! :) Но есть и некоторые минусы. Один из них, например, - качестве библиотек никто не гарантирует и надо тратить время на исследования того, годная ли библиотека вообще для использования. Как с этим бороться? Во-первых, конечно же, нужно советоваться с коллегами, если кто-то получил драгоценный опыт использования какой-либо системы, то неплохо бы его перенять. А во-вторых гарантию качества даёт кол-во библиотек использующих интересующую вас систему. Вот об этом и пойдёт речь в этой заметке.
  Итак, я рад представить сообществу ASDF-систему для оценки рейтингов open-source библиотек из репозитория Quicklisp. Рейтинг системы в данном контексте будет ничем иным, как кол-вом библиотек, которые её используют. Инструкцию по использованию читайте здесь: https://github.com/LinkFly/ql-libs-analizing/blob/master/README_ru.

Сам репозиторий: https://github.com/LinkFly/ql-libs-analizing
Проект в Redmine: http://linkfly.ru:8201/redmine/projects/ql-libs-analizing (временно изменено, см. ниже)

Считаю, это крайне полезная система особенно для тех кто строит бизнес, с упором на использование opensource решений.
Предложения по развитию и баг-репорты пишите в проект на github'е или в Redmine.

P.S. Совершенно внезапно перестал работать 1gb.ru, вместе со своими DNS-серверами, поэтому ссылка на Redmine временно будет следующая: http://178.140.218.145:8201/redmine/projects/ql-libs-analizing

Проект LISP-DEV-TOOLS. Сервис для работы с проектом в Redmine.

    И вот теперь, когда я вновь "свободный художник" займусь вероятно своими open-source проектами:)
Что касается конкретно lisp-dev-tools: вообще говоря, цель достигнута - это удобный инструмент для автоматизации разворачивания инфраструктуры для разработки на Common Lisp, я собственно сам им и пользуюсь. Судите сами: есть (по каким-либо причинам) у вас чистая ОСь (ну прямо девственно-чистая), и вам нужно получить всё самое необходимое для разработки на CL, причём не просто так - а согласно современному состоянию развития open-source инструментария для работы с Common Lisp'ом. Но вы не хотите совершать "лишних телодвижений" (я вот например, их о-о-очень не люблю делать), чтобы получить всё необходимое и обязательно сразу. Если вы пользуетесь lisp-dev-tools, то (даже в каком-нибудь jail'e с кучей ограничений) вы делаете так:

git clone https://github.com/LinkFly/lisp-dev-tools.git
cd lisp-dev-tools
./provide-slime

... И получаете ВСЁ!
Но при этом есть возможность тонко настроить версии инструментов. Например, мне в ближайшем будущем точно понадобится фича в emacs 24-ой версии, позволяющая установливать дополнительные пакеты для Emacs, устанавливаемые в духе пакетной системы дистрибутивов Linux'a и пакетной системы в Quicklisp. Я тупо наугад подобрал версию Emacs'a которая у меня успешно загрузилась/скомпилировалась и установилась внутрь lisp-dev-tools (в репозитории используемого мной дистрибутива 24-ой версии соотв. не оказалось). Номер версии я поменял в lisp-dev-tools/conf/tools.conf.

Кроме того, я иногда заглядываю в исходники sbcl'a и в этом случае, мне недостаточно простой (и быстрой) установки бинарной сборки - в этом случае, я дополнительно делаю:

./rebuild-lisp.sh

Собственно и всё. Параметры по умолчанию, безусловно можно настроить как душе угодно.

Далее, время от времени, по некоторым причинам мне нужно использовать другую лисп-систему и/или другую версию используемой лисп-системы и всё решается мгновенно, например:

./change-lisp ecl
./change-version 12.7.1

В любой момент, я временно могу запустить, например тот же SLIME с другой Лисп-системой:

LISP=sbcl ./run-slime

И так далее и тому подобное ...

А теперь у проекта появился, скажем так: "свой дом" в виде Redmine'овском сервиса по управлению проектами:

http://linkfly.ru:8201/redmine/projects/lisp-dev-tools

Я уже вполне его успешно использую этот проект, но некоторые фичи я бы ещё добавил.  В общем это "без 5-ти минут" мощнейший инструмент корпоративного класса и если общественность обратит на него внимание, то в самое ближайшее время - реально довести проект до "мажорной версии" и рекомендовать к использованию в производственных условиях.

Репозиторий на github.com: https://github.com/LinkFly/lisp-dev-tools

Гипервизор на голом железе ESXi - следующее поколение виртуализации.

Содержание:

1. Введение.
2. Некоторые возможности ESXi.
3. Управление пользователями и полномочиями.
4. Скриншоты.
5. Заключение.
6. Дополнение.
7. Ссылки.

---------------------------------------

1. Введение.

    Вначале пара слов о гипервизорах вообще и о понятии "на голом железе" в частности.

    Гипервизор это система управления виртуальными машинам. Как ни странно, встречаются до сих пор люди, незнакомые с понятием "виртуализация". Так что поясню, на всякий случай: виртуальная машина это модель реального компьютера с которой вы можете работать как с настоящим компьютером - ставить ОС, на неё программы и соотв. заходить на сайты, смотреть фильмы и даже играть в игры и прочее-прочее. Чувствую, обязательно следует упомянуть о снэпшотах, ибо был прецедент: в одной, казалось бы, серьёзной фирме, занимающейся разработкой банковского софта - использовалась система виртуализации VMware Workstation без задействования технологии снэпшотов вообще. А ведь это основное преимущество, которое предоставляют системы виртуализации! Итак, снэпшоты (snapshots) это сохранённые (причём идеально сохранённые) состояния виртуальной машины. Эти состояния, как не сложно догадаться, можно в любой момент восстановить. В VMware Workstation и тем более в ESXi можно создавать целые иерархии сохранённых состояний и возвращаться к ним в любой момент времени. Этакая машина времени - совершенно незаменимая вещь для разработчика и исследователя.

    Понятие "голое железо" (bare metal). В нашем контексте, это понятие означает что софт, о котором идёт речь, выполняется без какой-либо предустановленной операционной системы. Конкретно ESXi, представляет собой мини-операционную систему, "заточенную" для выполнение виртуальных машин и для управления ими. То есть это тонкий слой между реальными, физическими ресурсами и виртуальными машинами (и вообще виртуальными объектами, например виртуальными сетями). Зачем это нужно? Да так сразу и не расскажешь ... Ну в первую очередь, конечно же для получения преимуществ от упомянутых снэпшотов. Во вторых, для балансировки нагрузки. В третьих для упрощения администрирования ресурсов. В четвёртых для реализации более совершенной информационной безопасности. И это только первое что пришло ну ум. Итак, некоторые "фичи":
    - сохранение/восстановление состояний
    - балансировка нагрузки
    - упрощеное администрирование
    - безопасность
    
2. Некоторые возможности ESXi.

    Вот этот текст, кстати сказать, я набираю в Emacs'e, запущенном на рабочей виртуалке (которая, разумеется, на сервере с ESXi). Главный профит на данный момент лично для меня: в любой момент, не делая никаких лишних телодвижений (вообще), я могу сорваться и поехать куда-либо по делам захватив с собой ноутбук (и Yota'у для интернета). И далее, смогу продолжить работу в любом месте, где есть покрытие связи, предоставляемой Yota'ой (а оно есть практически по всей Москве). Я даже виртуалку в Suspend-режим переводить не буду! Кроме того, мне ведь могут понадобиться в процессе и другие виртуальные машины. Например на каком-либо этапе тестирования разрабатываемого софта, необходимо проверить работу в реализуемой виртуальными машинами тестовое среде (одной из). И самое главное: мне ведь необходимо время от времени корректировать тестовые среды, т.е. создавать/изменять виртуальные машины и их состояния(snapshots). В простых случаях, конечно, можно обойтись и другими решениями вроде использования удалённых рабочих столов и обычных не "bare metal" (не на голом железе) системами виртуализации. Но в случае когда кол-во информационных объектов и ресурсов, с которыми ведётся работа, велико - требуется более мощное решение.

    Итак, некоторые полезные фичи:
1) Пул ресурсов. Есть возможность создавать некие группы ресурсов (пулы ресурсов) и задавать параметры использования физических ресурсов сразу для всей группы. К тому же, эти группы можно вкладывать друг в друга и таким образом строить иерархию из групп и объектов виртуализации.
2) Настройка ограничений. Тонко настраиваемые ограничения на использование физических ресурсов. Любопытно: настройка ресурсов вплоть до указания конкретной тактовой частоты для виртуальных процессоров.
3) Резервирование ресурсов. Резервирование физических ресурсов для виртуальных машин и пулов ресурсов.
4) Управление доступом. Имеется весьма гибкая подсистема управления пользователями, группами, ролями, привилегиями и разрешениями.

    
3. Управление пользователями и полномочиями.

    Вот на управлении доступом остановимся по подробней. Эта подсистема, должен отметить, чрезвычайно гибкая и лаконичная. Вкратце, она состоит из следующих элементов:
        1) Пользователи. Могут входить в несколько групп.
    2) Группы. Содержат несколько пользователей.
    3) Роли. Содержат набор привилегий.
    4) Привилегии. Установленные привилегии, позволяют определённые действия в системе.
    5) Разрешения. Задаются для конкретного объекта (виртуальной машины или пула ресурсов) и состоят из пар пользователь_или_группа/роль.

    В общем схема такая:
        - создаём необходимых пользователей (вероятно, представляющих реальные личности, которых предполагается допустить к взаимодействию с ESXi), расфасовываем их по группам (например: admins, developers, vm-providers и т.д.).
        - безотносительно к пользователям и группам, создаём некоторое кол-во ролей - они в сущности представляют собой набор определённых привелегий (то есть набор допусков к определённым действиям в системе).
        - для конкретных элементов виртуализации, а именно для виртуальных машин и пулов ресурсов (содержащих виртуальные машины и другие пулы) назначаем разрешения. Под назначением разрешения здесь подразумевается добавление пользователей и групп а ассоциирование их (каждого пользователя и каждой группы) с определёнными ролями, причём каждый пользователь или группа может иметь только одну роль (считаю это разумный баланс между гибкостью и простотой использования).

4. Скриншоты.

Иерархия ресурсов и экран виртуальной машины:


Редактирование разрешений:


5. Заключение.


    Очевидно, что этой технологии самое место в бизнес-среде как либо связанной с IT (разумеется в случае, если требуется нечто большее чем заполнение данными электронной таблицы и печати документов в Word'e). Особенно это технология проявит себя в области разработки (и эксплуатации) промышленного software, так как позволяет эффективно управлять состояниями вычислительной среды, ресурсами и сократить разрыв между тестовой и продакшен (production) средами выполнения. Считаю, что овладение этой технологией и внедрение туда, где подобное пока ещё не используется - даст мощный технологический рывок вперёд.

6. Дополнение.

    И напоследок напишу ещё пару слов о некоторых "чудесах", предоставляемых платными вариантами гипервизора:

    VMotion - эта технология позволяет осуществлять "горячую миграцию" (Live Migration) виртуальных машин с одного физического хоста на другой, то есть перемещение виртуальных машин без их выключения и потери сетевых соединений.
    
    DRS (Distributed Resource Scheduler) - эта технология в сущности развитие идеи горячей миграции: контролируя работу, так называемых, DRS-кластеров (это неявное объединение реальных аппаратных ресурсов - физические хосты можно объединять в эти самые DRS-кластеры), она осуществляет автоматическое перемещение виртуальных машин на менее загруженных физические хосты, соответственно без остановки и без потери сетевых соеденений.

    HA (High Availability) - технология высокой доступности, в случае выхода из строя физического хоста, обеспечивает автоматический запуск аналогичных виртуальных машин на другом хосте.

    FT (Fault Tolerance) - технология отказоустойчивости. Представляет собой дальнейшее развитие идеи "высокой доступности (High Availability): На физическом хосте отличного от того на котором выполняется виртуальная машина, эта технология поддерживает точную зеркальную копию этой виртульной машины. Всё что происходит в оригинале, выполняется и в копии. Как только, выйдет из строя оригинальная виртульная машина (например из-за поломки физического хоста) - зеркальная копия подхватит её работу, полностью сохранив контекст выполнения и сетевые соединения (вместе с их состояниями).

    VCB (VMware Consolidated Backup) - это набор инструментальных средств и интерфейсов, обеспечивающих резервное копирование.

    vShild Zones - технология управления сетевым траффиком, проходящим через виртуальные коммутаторы. Позволяет применять политики безопасности для групп виртуальных машин. При осуществлении горячей миграции (посредством технологии vMotion и соотв. использующей её DRS) виртульных машин между физическими хостами - эти политики безопасности сохраняются.

    vCenter Orchestrator - это инструментарий автоматизации рабочих процессов, связанных с управлением гипервизорами, виртуальными объектами и перечисленными технологиями (на его основе был создан отдельный продукт - VMware vCenter Lifecycle Manager.
 
7. Ссылки.

1. Зарегистрироваться и скачать ESXi можно здесь.
2. Сайт корпорации разработчика: http://www.vmware.com/ru/
3. VMware ESXi Server - основа для центра данных.
4. vMind.ru - серверная виртуализация.
5. vmgu.ru.

Устройство ASDF. Оглавление.

Устройство ASDF. Подсистема определения путей. COMPUTE-OUTPUT-TRANSLATIONS.

(сказаное ниже относится к версии 2.019.8)

Это 19-ая статья цикла.
Первая статья.
Архитектура ASDF.
Предыдущая статья.

    Ф-ия COMPUTE-OUTPUT-TRANSLATIONS вычисляет значения для инициализации глобальной переменной *OUTPUT-TRANSLATIONS*. Эта ф-ия во многом схожа с ф-ией COMPUTE-SOURCE-REGISTRY, но несколько проще. В ней тоже работает некий конвейр обрабатывающий переменную среды и конфигурационные файлы, если они есть. А используемые ф-ии имеют созвучные с используемыми в COMPUTE-SOURCE-REGISTRY имена.

Логика её функционирования следующая:

COMPUTE-OUTPUT-TRANSLATIONS
(defun* compute-output-translations (&optional parameter) ...)
1. Создаётся контекст для последующего вызова ф-ии INHERIT-OUTPUT-TRANSLATIONS. В этом контексте на самом "верху" находится ф-ия REMOVE-DUPLICATES - она из получившегося результата удалит повторяющиеся элементы. Кроме того, используется макрос WHILE-COLLECTING определяющий локальную ф-ию C для сбора промежуточных результатов:

   (remove-duplicates
     (while-collecting (c)
       (inherit-output-translations ...)
     :test 'equal :from-end t
)


    Список с данными для конвейера состоит из символов именующих ф-ии, которые метод PROCESS-OUTPUT-TRANSLATIONS будет вызывать для получения конфигурационных данных. Во главе списка идёт символ WRAPPING-OUTPUT-TRANSLATIONS. Её вызов приведёт к получению таких конфигурационных данных, которые обеспечат лисп-системам сохранение скомпилированных системных файлов рядом с их исходниками, например для Linux/SBCL результат будет следующим:

       (wrapping-output-translations)
       => (:OUTPUT-TRANSLATIONS
        ((#P"/media/COMMON_LISP/lisp-dev-tools/lisp/sbcl/sbcl-1.0.53/lib/sbcl/"
        #P"**/"
)

         NIL
)

        :INHERIT-CONFIGURATION :ENABLE-USER-CACHE
)


    Далее следует имя ф-ии заданное аргументом PARAMETER (или соответственно NIL если PARAMETER не задан). Все остальные символы именующие функции, берутся из глобальной переменной *DEFAULT-OUTPUT-TRANSLATIONS*, которая по умолчанию равна:

        '(ENVIRONMENT-OUTPUT-TRANSLATIONS
          USER-OUTPUT-TRANSLATIONS-PATHNAME
          USER-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME
          SYSTEM-OUTPUT-TRANSLATIONS-PATHNAME
          SYSTEM-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME)

    Эти ф-ии имеют следующее предназначение:

    ENVIRONMENT-OUTPUT-TRANSLATIONS - возвращает данные, сохранённые в переменной среды "ASDF_OUTPUT_TRANSLATIONS".

    USER-OUTPUT-TRANSLATIONS-PATHNAME - возвращает путь к конфигурационому файлу пользователя если он реально существует. Имя файла определяется глобальной динамической переменной *OUTPUT-TRANSLATIONS-FILE*, которая по умолчанию равна #P"asdf-output-translations.conf". Если выполнение происходит на UNIX-like -системе (например на Linux), то поиск файла производится в следующих директориях:
            - в директории, начальный путь которой, задан через переменную среды "XDG_CONFIG_HOME" а окончание пути - "common-lisp/".
            - в одной из директорий начинающихся с директорий в переменной среды "XDG_CONFIG_DIRS" (директории разделяются символом ":") и заканчивающихся, опять же, на "common-lisp/".
        - в директории начинающейся с домашней директории пользователя и оканчивающейся на ./config/common-lisp/, например: /home/user/common-lisp/.config

    USER-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME - то же самое что и USER-OUTPUT-TRANSLATIONS-PATHNAME, но ищет не конфигурационный файл, а директорию для конфигурационных файлов по тем же путям поиска, с именем *OUTPUT-TRANSLATIONS-DIRECTORY* (по-умолчанию #P"asdf-output-translations.conf.d/").

    SYSTEM-OUTPUT-TRANSLATIONS-PATHNAME - то же самое что и USER-OUTPUT-TRANSLATIONS-PATHNAME, но файл заданный *OUTPUT-TRANSLATIONS-FILE* ищет в директории #P"/etc/common-lisp/".

    SYSTEM-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME - то же самое что и USER-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME, но ищет директорию заданную *OUTPUT-TRANSLATIONS-DIRECTORY* в директории #P"/etc/common-lisp/".

    Итак, вызов COMPUTE-OUTPUT-TRANSLATIONS, допустим с аргументом PARAMETER = NIL, приведёт к вызову INHERIT-OUTPUT-TRANSLATIONS со следующими аргументами:

        (INHERIT-OUTPUT-TRANSLATIONS
            '(WRAPPING-OUTPUT-TRANSLATIONS
             NIL
             ENVIRONMENT-OUTPUT-TRANSLATIONS
             USER-OUTPUT-TRANSLATIONS-PATHNAME
             USER-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME
             SYSTEM-OUTPUT-TRANSLATIONS-PATHNAME
             SYSTEM-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME)
     :COLLECT #<CLOSURE (FLET C) {1004A48AAB}>)

        ... на месте не печатаемого объекта "#<CLOSURE (FLET C) {1004A48AAB}>" разумеется будет что-то другое.


INHERIT-OUTPUT-TRANSLATIONS
(defun* inherit-output-translations (inherit &key collect) ...)
2. Конвейрная обработка организуется функцией INHERIT-OUTPUT-TRANSLATIONS и методами PROCESS-OUTPUT-TRANSLATIONS. Они рекурсивно вызывают друг друга (хотя в большинстве случаев PROCESS-OUTPUT-TRANSLATIONS обходится без "неявно-рекурсивного" вызова INHERIT-OUTPUT-TRANSLATIONS), по пути складывая в список промежуточные результаты с помощью локальной функции-коллектора C, определённой как сказано выше макросом WHILE-COLLECTING (в теле ф-ии INHERIT-OUTPUT-TRANSLATIONS она связывается с ключом COLLECT) . Когда аргумент INHERIT не равен NIL, что сигнализирует о необходимости запуска "конвейера", она разбивает его на голову и хвост и передаёт ф-ии PROCESS-OUTPUT-TRANSLATIONS:

    (when inherit
      (process-output-translations (first inherit) :collect collect :inherit (rest inherit)))

    ... хвост (rest inherit) передаётся для того, чтобы метод PROCESS-OUTPUT-TRANSLATIONS мог рекурсивно (через вызов INHERIT-OUTPUT-TRANSLATIONS) продолжить дальнейшую обработку, если в конфигурационном DSL был указан ключ :inherit-configuration.


PROCESS-OUTPUT-TRANSLATIONS
(defgeneric process-output-translations (spec &key inherit collect) ...)
3. Эта обобщёная ф-ия включает 5 методов специализирующихся на следующих типах аргумента SPEC: NULL, SYMBOL, STRING, PATHNAME и CONS. В конечном счёте всё сводится к вызову метода специализирующегося на типе CONS, так как аргумент в этом случае, будет содержать список, представляющий конфигурационные данные (ради получения и обработки которых, собственно всё и затевается).

    3.1 Сначала я рассмотрю ф-ии на которых базируются разные специализации PROCESS-OUTPUT-TRANSLATIONS, после понимания работы этих ф-ий - анализ взаимодействия методов станет тривиальным.

        VALIDATE-CONFIGURATION-FILE
        (defun* validate-configuration-file (file validator &key description) ...)
        3.1.1 Ф-ия считывает с указанного файла FILE список, проверяет что он в файле единственный и вызывает функцию-валидатор VALIDATOR для проверки корректности данных. Ключ DESCRIPTION ей необходим в случае неудачи, для формирования сообщения об ошибке.

        VALIDATE-CONFIGURATION-DIRECTORY
        (defun* validate-configuration-directory (directory tag validator &key invalid-form-reporter) ...)
        3.1.2 А эта ф-ия ищет в указанной директории DIRECTORY файлы с расширением .conf (пропуская скрытые файлы), считывает из них множество форм, представляющих директивы анализируемого DSL (Domain Specific Language - язык предметной области), проверяет каждую их них функией-валидором VALIDATOR и составляет из них правильный формат конфигурационных данных (который, соответственно, возвращается в качестве результата), используя при этом,  переданный ключевой символ TAG (он становится первым элементом результирующего списка).

        VALIDATE-OUTPUT-TRANSLATIONS-FILE
        (defun* validate-output-translations-file (file) ...)
        3.1.3 Ф-ия для считывания и проверки конфигурационных данных обращается за помощью к ф-ии VALIDATE-CONFIGURATION-FILE, которая в качестве валидатора использует функцию VALIDATE-OUTPUT-TRANSLATIONS-FORM. Если проверка корректности данных прошла успешно, то они возвращаются в качестве результата.

        VALIDATE-OUTPUT-TRANSLATIONS-DIRECTORY
        (defun* validate-output-translations-directory (directory) ...)
        3.1.4 Для поиска файлов с конфигурационными данными в директории DIRECTORY, их считывания и проверки вызывается функция VALIDATE-CONFIGURATION-DIRECTORY. В качестве функции-валидатора она использует VALIDATE-OUTPUT-TRANSLATIONS-DIRECTIVE, проверяющей каждую прочитанную форму из конфигурационных файлов (директиву анализируемого DSL).

        PARSE-OUTPUT-TRANSLATIONS-STRING
        (defun* parse-output-translations-string (string &key location) ...)
        3.1.5 Эта ф-ия используется для преобразования данных их переданной строки в правильно сформированные конфигурационные данные. Ф-ия анализирует переданный аргумент и выбирает одно из следующих действий:
            - если STRING не является строкой, то ф-ия сигнализирует об ошибке.
            - если STRING равна пустой строке или NIL то вызвращается список '(:output-translations :inherit-configuration).
            - если STRING содержит вложенную строку (то есть, строку вида "\"...\""), то эта вложенная строка "вынимается" из STRING с помощью вызова (read-from-string string) и повторяется всё заново со вложенной строкой (т.е. ф-ия рекусивно вызывается с получившейся строкой).
            - если первый символ в STRING это "(", то значит в строке содержится список и тогда он читается из строки, с помощью того же вызова (read-from-string string) и также происходит рекурсивный вызов с получившимся результатом.
            - если же не сработал ни один из получившихся вариантов, тогда строка рассматривается как последовательное перечисление директорий, каждая их которых разделяется специфичным для системы симолом-разделителем директорий (для Linux это символ ":"). Причём кол-во директорий должно быть обязательно чётным, иначе сигнализируется ошибка. Отсутствие текста между двумя символами-разделителями (например: "/home/src:/some/compiled::") говорит об использовании ключа :INHERIT-CONFIGURATION (его допускается использовать один раз, при нарушении этого правило сигнализируется ошибка).

        VALIDATE-OUTPUT-TRANSLATIONS-FORM
        (defun* validate-output-translations-form (form &key location) ...)
        3.1.6 Ф-ия проверяет корректность конфигурационных данных с помощью ф-ии VALIDATE-CONFIGURATION-FORM. Для проверки каждой директивы она использует ф-ию VALIDATE-OUTPUT-TRANSLATIONS-DIRECTIVE. Ключ LOCATION используется в случае возникновения ошибки и содержит небольшую часть сообщения.

        VALIDATE-CONFIGURATION-FORM
        (defun* validate-configuration-form (form tag directive-validator
                          &key location invalid-form-reporter) ...)
    3.1.7 Ф-ия проверяет корректность конфигурационных данных, представленных аргументом FORM. В случае если аргумент FORM не является списком или его первый элемент не эквивалентен аргументу TAG, то глобальная переменная *IGNORED-CONFIGURATION-FORM* получает значение T и сигнализируется ошибка. Основная часть ф-ии состоит из итерирования по хвосту списка FORM. Ф-ия проверяет каждую директиву переданым валидатором DIRECTIVE-VALIDATOR, при этом она проверяет что ни один из ключей :INHERIT-CONFIGURATION или :IGNORE-INHERITED-CONFIGURATION не встречается более одного раза. А также она обрабатывает семантику ключа :IGNORE-INVALID-ENTRIES позволяющего игнорировать неправильные директивы, не сигнализируя ошибки.

        VALIDATE-OUTPUT-TRANSLATIONS-DIRECTIVE
        (defun* validate-output-translations-directive (directive) ...)
        3.1.8 Ф-ия отвечает непосредственно за валидацию директивы (обрабатываемого DSL). Если аргумент DIRECTIVE атом, то он обязан иметь только одно из следующих значений: :ENABLE-USER-CACHE, :DISABLE-CACHE, NIL. Если же он является списком, тогда считается корректной одна из следующих ситуаций:

            3.1.8.1 Длина списка = 2 и при этом:
                - либо в качестве первого элемента используется ключ :INCLUDE, а в качестве второго - объект типа STRING, PATHNAME или NIL.
                - либо первый элемент удовлетворяет предикату LOCATION-DESIGNATOR-P, а второй - либо предикату LOCATION-DESIGNATOR-P, либо предикату LOCATION-FUNCTION-P.                    

            3.1.8.2 Длина списка = 1 и его единственный элемент удовлетворяет предикату LOCATION-DESIGNATOR-P.

            Используемые предикаты работают следующим образом:

            LOCATION-DESIGNATOR-P
            Предикат возвращает значение T, в случае если аргумент:
                - атом и имеет тип STRING, PATHNAME или одно из следующих значений:  T, NIL, :ROOT :HOME :HERE :USER-CACHE :SYSTEM-CACHE :DEFAULT-DIRECTORY.
                - список, и тогда в качестве первого элемента в списке должно быть то, что описано выше, а в качестве оставшихся должны быть элементы типа STRING, PATHNAME, или следующие значения: :DEFAULT-DIRECTORY, :*/, :**/, :*.*.*, :IMPLEMENTATION, :IMPLEMENTATION-TYPE.

            LOCATION-FUNCTION-P
            Предикат возвращает T, в случае если аргумент:
                - список из двух элементов, первый из которых это :function, а второй: либо символ, либо список, представляющий лямбда-выражение, принимающее на вход два параметра.
        
        PROCESS-OUTPUT-TRANSLATIONS-DIRECTIVE
        (defun* process-output-translations-directive (directive &key inherit collect) ...)
        3.1.9 Это самая важная ф-ия, она занимается непосредственно обработкой директивы из, предварительно проверенных на корректность, конфигурационных данных. Смысл её в том, чтобы разобрать (распарсить) переданную директиву и сформировать данные для последующего помещения в результирующий список с помощью ф-ии передаваемой ключом COLLECT. Результаты обработки в конце-концов попадут в глобальную переменную *OUTPUT-TRANSLATIONS*, а она в свою очередь, необходима для работы ф-ии APPLY-OUTPUT-TRANSLATIONS (это ф-ия, вычисляющая пути к файлам содержащим результаты применения операций к компонентам - обычно к *.fasl файлам). В процессе работы ф-ия может рекурсивно вызвать сама себя или ф-ию INHERIT-OUTPUT-TRANSLATIONS с параметром INHERIT, если была вызывана с аргументом DIRECTIVE равным ключу :INHERIT-CONFIGURATION. Подробный разбор логики работы этой ф-ии будет дан в конце статьи.

    3.2 Теперь вернёмся к обобщённой ф-ии PROCESS-OUTPUT-TRANSLATIONS, её специализации работают следующим образом:

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS NULL)
Осуществляется переход к обработке следующего элемента:

    (inherit-output-translations inherit :collect collect)

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS SYMBOL)
Вызывается ф-ия, имя которой представлено символом, для получения конфигурационных данных для дальнейшей обработки:

    (process-output-translations (funcall x) :inherit inherit :collect collect)

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS STRING)
Аргумент обрабатывается ф-ией PARSE-OUTPUT-TRANSLATIONS-STRING, возвращающей конфигурационные данные для дальнейшей обработки:

    (process-output-translations (parse-output-translations-string string)
                 :inherit inherit :collect collect)

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS PATHNAME)
Если аргумент представляет собой файл, то для чтения конфигурационных данных и их валидации (и возврата в качестве результата) вызывается ф-ия VALIDATE-OUTPUT-TRANSLATIONS-FILE. Если аргумент содержит путь к директории, то файлы в ней рассматриваются как содержащие конфигурационные данные. За сбор этих данных и их валидацию отвечает ф-ия VALIDATE-OUTPUT-TRANSLATIONS-DIRECTORY. Если аргумент представляет собой не существующий файл, то происходит переход к дальнейшей обработки:

    (inherit-output-translations inherit :collect collect)

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS CONS)
В этом случае аргумент представляет собой конфигурационные данные. Производится их валидация и каждый элемент этих данных обработывается ф-ией PROCESS-OUTPUT-TRANSLATIONS-DIRECTIVE:

    (dolist (directive (cdr (validate-output-translations-form form)))
      (process-output-translations-directive directive
                         :inherit inherit
                         :collect collect
)
)


--------------------------------------------------------------
        
PROCESS-OUTPUT-TRANSLATIONS-DIRECTIVE
(defun* process-output-translations-directive (directive &key inherit collect) ...)
Это главная ф-ия, используемая в процессе обработки конфигурационных данных, используемых при формирования путей результатов применения операций к компонентам. Эти конфигурационные данные следуют специальным соглашениям ASDF для формирования этих путей, представляющих собой некий мини-DSL.

Логика работы:
    
1. Если аргумент DIRECTIVE - атом, то в зависимости от его значения-ключа происходит следующее:
    - :ENABLE-USER-CACHE - рекурсивный вызов с DIRECTIVE = '(t :user-cache)
    - :DISABLE-CACHE - рекурсивный вызов с DIRECTIVE = '(t t)
    - :INHERIT-CONFIGURATION - вызов INHERIT-OUTPUT-TRANSLATIONS с аргументом INHERIT.
    - для аргументов :IGNORE-INHERITED-CONFIGURATION, :IGNORE-INVALID-ENTRIES, NIL - возвращает NIL.

2. Если же аргумент список определяется локальное окружение с переменными, содержащими элементы списка:

   (let ((src (first directive))
      (dst (second directive))
)

      ...
)


3. Далее, если SRC = :INCLUDE то путь в DST обрабатывается "неявно-рекурсивным" вызовом ф-ии PROCESS-OUTPUT-TRANSLATIONS:

    (if (eq src :include)
        (when dst
     
     (process-output-translations (pathname dst) :inherit nil :collect collect))
    ...
)


4. Иначе, если переменная SRC не равна ни :INCLUDE ни NIL, то происходит следующее:

    4.1 Создаётся локальное окружение с переменной TRUSRC. Она становится равной либо T (если SRC = T):

       (let ((trusrc (or (eql src t)
                           
  ...)))
        ...
)


        ... либо её значение вычисляется из локальной SRC, которая должна представлять собой или путь к файлу непосредственно, или список описывающий путь (оформленный с помощью специальных соглашений ASDF по формированию путей):

       (let ((loc (resolve-location src :directory t :wilden t)))
          (if (absolute-pathname-p loc) (truenamize loc) loc))

        ... как видно используется ф-ия RESOLVE-LOCATION которая воспринимает SRC как директорию и создаёт, так называемый "wildcard" путь (путь использующий групповые символы). Например, вот результат применения RESOLVE-LOCATION с путём "/usr/somedir":

        (resolve-location "/usr/somedir" :directory t :wilden t)
        => #P"/usr/somedir/**/*.*"

        4.2 В созданном локальном окружении, возможные варианты выполнения распадаются на три ветки:
            
            4.2.1 Если переменная DST удовлетворяет предикату LOCATION-FUNCTION-P (т.е это список имеющий первый элемент :FUNCTION, а второй - либо символ, либо список с лямбда-выражением), то в результирующий список (собираемый ф-ией переданной в ключе COLLECT) попадает список из TRUSRC и из функционального объекта представленного вторым элементом DST:

               (cond
                  ((location-function-p dst)
                   (funcall collect
                            (list trusrc
                                  (if (symbolp (second dst))
                                      (fdefinition (second dst))
                                      (eval (second dst))
)
)
)
)

                  ...
)


            4.2.2 Если DST = T, то возвратить список из TRUSRC и T:

               (cond
         
         ...
       
           ((eq dst t)
              
       (funcall collect (list trusrc t)))
        
          ...)

            4.2.3 В остальных случаях в результирующий список попадают 2 списка:
                - список из обработанной (с помощью ф-ии RESOLVE-LOCATION) переменной DST если она не была равна NIL (а если была, то в список попадает значение переменной TRUSRC) и константы T.
                - и список из TRUSRC и обработанной DST (в случае если значение DST было равно NIL, то в списке оказывается значение TRUSRC).

--------------------------------------------------------
Продолжение следует ...

Заповедник для Лисп'ов - проект LISP-DEV-TOOLS.

    Захотелось как-то раз мне сделать один весьма не тривиальный софт ... Да ещё так, чтобы работал так как я хочу, не смотря на множество технических проблем, препятствующих реализации задуманного. И чтобы был полезен не только мне, но и другим лисп-разработчикам, причём как новичкам так и проффесионалам. И чтобы проект имел шанс на светлое будущее и поддержку сообществом. А вообще много было всяких "чтобы ...". И самое приятное в этом то, что удалось всё задуманное реализовать и даже больше!
    Итак, рад представить первый релиз проекта LISP-DEV-TOOLS. Хоть это только первый релиз проекта, он уже вполне рабочий и должен без каких-либо проблем успешно использоваться в конфигурациях Ubuntu Linux 11.04/x86-64 и Linux Mint 11/x86-64. Тестирование/отладка для других дистрибутивов планируются и не должны быть трудоёмкими (скорость процесса будет зависеть от количества заинтересованных лиц). Привествуются любые идеи, пожелания и особенно помощь в тестировании и патчи.
    Этот проект будет весьма полезен лисп-разработчикам и исследователям, которым приходиться иметь дело с разными лисп-системами (работая с ОС базирующихся на GNU/Linux).

Репозиторий: https://github.com/LinkFly/lisp-dev-tools
Ссылка на README_ru проекта: https://github.com/LinkFly/lisp-dev-tools/blob/master/README_ru
Ссылка для замеченных проблем и пожеланий: https://github.com/LinkFly/lisp-dev-tools/issues

В README отображено далеко не всё, вопросы/предложения касающиеся проекта можно задать мне лично, написав на ящик linkfly1 at newmail.ru или отправив сообщение через github.com.

Перевод OnLisp

    Продолжился перевод книги знаменитого лиспера и эссеиста Пола Грэхэма: On Lisp. Добро пожаловать желающим поучаствовать и оставить след в истории:)

https://github.com/rigidus/onlisp

Координирование перевода ведёт автор репозитория.

TRACKING-CHANGES - отслеживание изменений.

  В прошой заметке, я писал о системе ASDF-TRACKING-CHANGES, расширяющей функциональность ASDF. Это система позволяет отслеживать изменения произошедшие в лисп-системе после выполнения операции с файлами (конкретно - после их компиляции и загрузки в лисп-систему). Непосредственно за отслеживание изменений отвечает её "запчасть" - система TRACKING-CHANGES. Правда, пока (и соответственно asdf-tracking-changes тоже) она отслеживает только появление новых пакетов. Она имеет простой и интуитивно понятный интерфейс, чтобы сохранить произошедшие при выполнении кода изменения, нужно этот код обернуть макросом with-monitoring, указав ключ для последующего поиска изменений. Например:

(tracking-changes:with-monitoring :my-key (load "some-file.lisp"))

Теперь список, созданных после выполнение формы (load "some-file.lisp") пакетов можно получить так:

(tracking-changes:get-sandbox-packages-list :my-key)

Впрочем, более подробно можно почитать в README.
Ссылка на github репозитарий: https://github.com/LinkFly/tracking-changes

Устройство ASDF. Подсистема определения. PARSE-COMPONENT-FORM.

UPDATED 11.12.2011. Разработчиками были удалены декораторы над ф-ией REINITIALIZE-INSTANCE. А также упрощён код связанный с повторной инициализацией или созданием объекта класса/подкласса COMPONENT.

UPDATED 14.12.2011. Скорректирован код обрабатывающий ключ :weakly-depends-on. Упомянуто отображение в документации того, что ключ :weakly-depends-on имеет смысл для верхнего уровня определения системы.

(сказаное ниже относится к версии 2.019.5)

Это 4-ая статья цикла.
Первая статья.
Архитектура ASDF.
Предыдущая статья.

    Функция PARSE-COMPONENT-FORM вызывается из ф-ии do-defsystem и представляет собой следующий и главный этап определения системы. Ф-ия строит иерархию объектов (класса component и его наследников) на основе передаваемых опций и присоединяет её к другому объекту, её определение выглядит так:

    (defun* parse-component-form (parent options) ...)

В parent передаётся объект к которому нужно присоединить создаваемую иерархию, в options передаются ключи управляющие созданием объектов. Если parse-component-form вызывается из do-defsystem, parent будет равен nil (это означает, что будет создаваться корневой объект иерархии). Список options выглядит подобно следующему:

    (:module "exp-system"
      :pathname #P"/home/someuser/lisp/asdf-experiments/"
      :depends-on nil
      :components ((:module "src"
                                  :pathname ""
                                  :components ((:file "file1")
                                                            (:static-file "static.txt")
                                                            (:file "file2" :depends-on ("file1"))
                                                            (:file "file3" :depends-on ("file1"))))))

    ... это те же опции, что используются в форме (defsystem ...) в *.asd файлах, но за исключением опции :class (так как, если она была задана, её обработка произошла до вызова parse-component-form в ф-ии do-defsystem).

            Логика работы parse-component-form.

1. Ф-ия с помощью destructuring-bind разбирает переданные параметры и устанавливает локальные переменные соответствующие их ключам. Есть правда, небольшое исключение: первые два элемента считаются обязательными (а не ключевыми) и локальными переменными для них будут type и name. Для примера выше (при разборе options) установки этих переменных будут следующие:

type = :module
name = "exp-system"

    Остальные имена локальных переменных будут соответствовать переданным ключам. Ключи :perform :explain :output-files :operation-done-p используются для создания инлайн-методов (inline methods) специализирующихся на этом компоненте, но их обработка происходит вне определения parse-component-form (конкретно в ф-ии %define-component-inline-methods вызываемой из %refresh-component-inline-methods, которая в свою очередь вызывается в конце вызова parse-component-form) и поэтому они помечены как ignorable (игнорируемые) чтобы подавить ненужные предупреждения. Вообще список возможных инлайн-методов содержится в константе +asdf-methods+. Список содержит символы именующие методы, соответственно упомянутым ключам (а также символ perform-with-restarts, соответствующий недокументированному инлайн-методу). Итак остаются следующие ключи:

    Задающие содержимое, путь, и класс компонента по умолчанию:
:components
:pathname
:default-component-class

    Задающие зависимости:
:weakly-depends-on
:depends-on

    Управляющие порядком операций:
:serial
:in-order-to
:do-first

    Дополнительные:
:version

    Чтобы вы при чтении дальнейшего описания, примерно представляли о чём идёт речь (конечно же, для более обстоятельного объяснения стоит обратится к официальной документации) ниже дано короткое описание, назначения опций:

    Задающие содержимое, путь, и класс компонента по умолчанию:
:components - компоненты, содержащиеся в данном (например файлы исходников или другие модули).
:pathname - переопределённый путь для компонента.
:default-component-class - класс, которорый будет использоваться при задании типа :file

    Задающие зависимости:
:weakly-depends-on - зависимости загружаются только в случае, если удалось их найти.
:depends-on - зависимости обязательные к загрузке.

    Управляющие порядком операций:
:serial - каждый описанный компонент, становится автоматически зависимым от предыдущего компонента.
:in-order-to - этой опцией можно переопределить порядок применения операций к компонентам.
:do-first - недокументированный ключ, также служит для тонкой настройки, порядка применения операций.

    Дополнительные:
:version - версия компонента (должна быть выше чем может быть указано в зависимостях от этого компонента).

    Кроме того, реализация позволяет использовать дополнительные ключи (для каких-нибудь собственных мета-надстроек), чуть позже список этих дополнительных ключей будет связан с лексической переменной args.

2. Далее parse-component-form вызывает ф-ию check-component-input для проверки значений, связанных с лексическими переменными weakly-depends-on, depends-on, components и in-order-to.

    (check-component-input type name weakly-depends-on depends-on components in-order-to)

    ... значения type и name передаются лишь для формировании сообщения об ошибке.  Проверка не сложная:
        - все проверяемые элементы должны быть списком - это раз (пусть даже и пустым).
        - если in-order-to не пустой список, первый его элемент должен быть тоже списком - это два.

3. Дальше идёт проверка того, что если определяемый компонент уже существует на том же уровне иерархии (а именно в компоненте parent), то он такого же типа, что и определяемый (иначе сигнализируется ошибка):

   (when (and parent
                        (find-component parent name)
                       ;; ignore the same object when rereading the defsystem
                        (not
                           (typep (find-component parent name)
                                       (class-for-type parent type)
)
)
)

      (error 'duplicate-names :name name)
)


    В первом вызове parse-component-form аргумент parent равен nil, поэтому проверка сразу пропускается. А вообще, суть проверки такова: если parent не nil и компонент найден в parent и тип компонента отличается от указанно типа, то имеет место коллизия имён и выбрасывается ошибка duplicate-names.
    Но почему здесь не сигнализируется ошибка, если был найден компонент того же типа и с тем же именем что и определяемый? Это было сделано для ситуации повторного чтения определения системы (например, если файл .asd изменился). Дело в том, что хэш-таблица в слоте components-by-name, объекта parent (который должен иметь тип/подтип module), используемая в методе find-component, будет содержать (при переопределении системы) старые записи компонентов. И конечно, найдется компонент с тем же именем, что и определяемый. Как видно, разработчики сделали так, чтобы сигнализация ошибки при изменении типа компонентов происходила пораньше. Непосредственно проверка того, что на том же уровне иерархии нет компонентов с одинаковым именем, осуществляется в ф-ии compute-module-components-by-name. Эта ф-ия выполняет итерацию по содержимому слота components (объекта класса/подкласса module) с тем, чтобы создать и заполнить хэш-таблицу с записями вида имя_компонента-компонент и записать её в слот components-by-name. а также сигнализировать ошибку duplicate-names, если встретились компоненты с одинаковым именем. Она будет вызвана здесь же, в parse-component-form, если определяемый компонент имеет тип/подтип module.
    В показаном выше коде, исопльзуется ф-ия class-for-type. Её определение достаточно тривиально, но имеет важный нюанс: используется слот default-component-class передаваемого объекта parent, а при равенство его NIL - динамическая переменная *default-component-class*.

    (defun* class-for-type (parent type) ...)
    CLASS-FOR-TYPE работает следующим образом:
        - пытаемся найти класс представленный символом type, сначала в пакете символа, затем в текущем пакете и наконец в пакете :asdf :

       (loop :for symbol :in (list
                                               type
                                              (find-symbol* type *package*)
                                              (find-symbol* type :asdf)
)

         :for class = (and symbol (find-class symbol nil))
         :when (and class (subtypep class 'component))
         :return class
)


        - для типа :file делается исключение, для него не обязательно иметь класс. При его использовании инстанцируемый класс выбирается следующим образом - если в слоте компонента default-component-class есть значение, то это будет возвращаемым значением, если нет, то значением будет класс *default-component-class*, который по умолчанию равен CL-SOURCE-FILE:

   (and (eq type :file)
             (or (module-default-component-class parent)
                   (find-class *default-component-class*)
)
)


Логика работы find-component здесь рассматриваться не будет, так как это тема для отдельной статьи.

4. Если была задана опция с ключом :version, то осуществляется проверка синтаксической корректности заданной версии. Это должна быть строка, содержащая числа, разделённые точками:

   (when versionp
      (unless (parse-version version nil)
        (warn ... )
)
)

5. Дополнительные ключи связываются с лексической переменной args:

   (let* ((args (list* :name (coerce-name name)
                        :pathname pathname
                        :parent parent
                        (remove-keys (remove-keys '(components pathname ... )
                                                             rest
)
)
)
               ...
)

      ...
)

 
    Эти ключи и их значения будут участвовать в создании (или повторной инициализации) компонента. А именно дополнительные аргументы передаются в make-instance (если компонент ещё не был создан) или в reinitialize-instance (если компонент был получен, после успешного поиска в parent), но об этом позже.

6. Лексической переменной ret присваивается компонент, если он уже был создан или конкретней: присваивается компонент с именем name содержащейся в parent:

   (let* (...
            (ret (find-component parent name))
)

      ...
)


    Если это первый вызов parse-component-form и соотв. аргумент parent равен nil, а аргумент name соответствует имени определяемой системы (оно сейчас содержится в переменной name и было передано через ключевой параметр :module) - вызов вернёт объект представляющий эту систему. Если же parent и name заданы (не равны nil), то производится поиск компонента в parent. Это нужно для того, чтобы заново не пересоздавать уже готовые объекты (а значит не выделять заново для них память, что важно).

7. Теперь обрабатывается ключик :weakly-depends-on - фактически это не что иное как список "не обязательных" систем:

   (when weakly-depends-on
      (appendf depends-on (remove-if (complement #'(lambda (x) (find-system x nil))) weakly-depends-on)))

В этом коде происходит присоединение к depends-on тех систем которые получилось найти. Принцип такой: не нашли, значит обойдёмся. С какой стати "систем", ведь функция parse-component-form вызывается (как мы увидим позже) вообще для всех элементов системы? Очевидно ключ :weakly-depends-on имеет право быть только в форме верхнего уровня (по отношению к форме (defsystem ...). Если его указать для какого-то вложенного компонента, то логично предположить что будут подгружаться системы соответствующие именам в этом списке, что врятли соответствует ожиданиям разработчика. Видимо авторам следовало бы либо изменить поиск систем на поиск компонентов/файлов либо ввести проверку на отсутствия ключа :weakly-depends-on в описании вложенных компонентов (впрочем, то что этот ключ имеет смысл для верхнего уровня определения системы - теперь, начиная с версии 2.019.5, отображено в документации).

8. Далее используется динамическая переменная *serial-depends-on* - если её содержимое не равно nil, это содержимое добавляется в depends-on:

   (when *serial-depends-on*
       (push *serial-depends-on* depends-on)
)


    По умолчанию *serial-depends-on* = nil, позже мы увидим в какой ситуации это будет не так. Вообще эта переменная работает совместно с ключом :serial - она содержит предыдущий, определёный в parse-component-form, компонент (на том же уровне иерархии) и как видно выше модифицирует список depends-on компонента включая туда этот компонент.

9. Далее, создаётся или переинициализируется объект класса/подкласса component:

    9.1 Если компонент был найден (при первом вызове, это понятное дело объект класса system или его наследника), то его необходимо повторно инициализировать, используя для этого, в том числе, дополнительные опции:

       (if ret ; preserve identity
         (apply 'reinitialize-instance ret args)
         ...)


    9.2 Если компонента в parent не было найдено - создаётся новый объект типа, имя которого связано с локальной type. Причём создаётся натурально из указанного типа, например если у вас в определении системы указан :module создаётся объект класса module. Для получения класса по type используется уже рассмотренная выше ф-ия class-for-type. То есть, совершенно свободно можете определять свои классы в иерархии наследования которых есть класс component и использовать в списках, внутри списка опции :components (исключение составляет, как показно выше в описании ф-ии class-for-type, ключ :file):

       (if ret
            (...)
         
(setf ret (apply 'make-instance (class-for-type parent type) args)))

10. Для компонента вычисляется значение слота absolute-pathname: (component-pathname ret). Принцип такой: по пути к самому старшему предку в иерархии, которым должна быть система, собираются именя компонентов и присоединяются к абсолютному пути этого корневого компонента, то есть системы. Для объекта-системы же, этот слот получает значение из слота relative-pathname, который должен быть абсолютным и вычисляется ещё в do-defsystem, а связывается со слотом во время повторной инициализации.

11. Далее, если компонент класса 'module (или его наследника) то выполняются следующие действия:

    11.1 Вычисляется слот 'default-component-class:

       (setf (module-default-component-class ret)
                (or default-component-class
                      (and (typep parent 'module)
                               (module-default-component-class parent)
)
)
)


        Как видно из кода он либо берётся из ключа :default-component-class либо из соответствующего слота своего предка.
        
    10.2 Затем, на основе списков в значении ключа :components создаётся список с объектами созданными из этих списков и присваивается слоту 'components:

           (let ((*serial-depends-on* nil))
               (setf (module-components ret)
                     (loop
                        :for c-form :in components
                        :for c = (parse-component-form ret c-form)
                        :for name = (component-name c)
                        :collect c
                        :when serial :do (setf *serial-depends-on* name)
)
)
)


        Обратите внимание, что создаётся локальный контекст в котором *serial-depends-on* приравнивается к nil, а каждый объект создаётся с помощью рекурсивного вызова всё той же parse-component-form (но уже в качестве parent выступает текущий объект). Здесь мы видим принцип работы ключа :serial - если он задан, то parse-component-form выполняется в контексте в котором *serial-depends-on* приравнена к предыдущему созданному компоненту, это влияет на форму (описанную в пункте 8):

        (when *serial-depends-on*
           (push *serial-depends-on* depends-on)
)


     ... то есть модифицирует значение depends-on, добавляя к нему имя предыдущего созданного компонента.

     11.3 Заполняется слот components-by-name создаваемой хэш-таблицей для быстрого поиска компонентов по имени:

       (compute-module-components-by-name ret)

     Там же осуществляется проверка на уникальность имён компонентов.

Дальнейшие действия происходят не только для объектов класса/подкласса module.

12. Далее устанавливается слот load-dependencies:

   (setf (component-load-dependencies ret) depends-on)

    ... в значение depends-on которое как мы помним могло быть модифицировано формами:

   (when weakly-depends-on
      (appendf depends-on (remove-if (complement #'find-system) weakly-depends-on))
)

   (when *serial-depends-on*
      (push *serial-depends-on* depends-on)
)


13. Теперь будет уставка слота in-order-to:

     (setf (component-in-order-to ret)
              (union-of-dependencies
                in-order-to
               `((compile-op (compile-op ,@depends-on))
                  (load-op (load-op ,@depends-on))
)
)
)


    Тело функции union-of-dependencies выглядит довольно хитро. Подробности её внутреннего устройство тема для отдельной статьи. Для начала следует иметь в виду, что она просто возвратит свой второй аргумент если опция :in-order-to не была установлена, а значит в этом случае слот in-order-to получит значение:

    `((compile-op (compile-op ,@depends-on))
       (load-op (load-op ,@depends-on)))  

14. Работа со слотом do-first происходит аналогичным образом:

    (setf (component-do-first ret)
             (union-of-dependencies
                do-first
                `((compile-op (load-op ,@depends-on)))))

    ... т.е. если опция :do-first не использовалась, то в слоте do-first сохраняется более ясное для понимания:

    `((compile-op (load-op ,@depends-on)))

15. Далее происходит следующее: обновляются, так называемые inline методы для компонента:

    (%refresh-component-inline-methods ret rest)

    При выполнении этой формы удаляются инлайн-методы компонента и определяются заново:

    15.1 Сначала удаляются все методы сохранённые в слоте inline-methods из обобщённых функций, сохранённых в
константе +asdf-methods+:

        (%remove-component-inline-methods component)

        Код этой функции достаточно тривиален и я не буду его здесь приводить.

    15.2 Затем слот inline-methods получает новый список методов используя для этого список оставшихся опций:

        (%define-component-inline-methods component rest)

        Код этой ф-ии тоже не сложный - для каждого символа в +asdf-methods+ создаётся соответствующий keyword:

        (dolist (name +asdf-methods+)
          (let ((keyword (intern (symbol-name name) :keyword)))
           ...
)
)


        Потом на каждой итерации происходит проход по списку опций компонента

         (loop :for data = rest :then (cddr data) ...)

        ...  и для каждого ключа из списка:

        (:PERFORM-WITH-RESTARTS :PERFORM :EXPLAIN :OUTPUT-FILES :OPERATION-DONE-P)

        ... генерируется и выполняется код создающий метод на основе значения ассоциированного с ключом:

       (eval `(defmethod ,name ,qual ((,o ,op) (,c (eql ,ret)))
                     ,@body
)
)


        Это было неожидано, кстати. И потом, как можно догадаться, он кладётся в список слота inline-methods.

16. Возвращение созданного компонента в качестве результата.

Для более ясной картины опишу вкратце все 16 действий, выполняемые parse-component-form:

1. Разбор ключевых параметров с помощью destructuring-bind.
2. Проверка того, что опции weakly-depends-on depends-on components in-order-to заданы правильными значениями (списками).
3. Проверка на отсутствие или существование компонента только того-же типа на этом же уровне иерархии.
4. Проверка на правильное задание ключа :version.
5. Получение дополнительных ключей.
6. Попытка найти старый компонент.
7. Модифицирование зависимостей depends-on, в соотвии со слабыми зависимостями, задаваемыми ключом weakly-depends-on.
8. Добавление зависимости от предыдущего компонента, если необходимо (задана опция :serial t).
9. Создание или переинициализация компонента:
      9.1 Если компонент найден при первом вызове, то - переинициализация.
      9.2 Если не был найден, то - создание.
10. Вычисление слота absolute-pathname.
11. Получение компонента по умолчанию, создание компонентов, инициализация слота components-by-name:
      11.1 Вычисление слота default-component-class по заданной опции или по слоту предка.
      11.2 Создание компонентов на основе значения опции :components.
      11.3 Инициализация слота components-by-name для быстрого поиска компонентов.
12. Установка слота load-dependencies скорректированным значением depends-on.
13. Установка слота in-order-to.
14. Установка слота do-first.
15. Обновление инлайн-методов.
      15.1 Удаление инлайн-методов в ф-ии %remove-component-inline-methods.
      15.2 Определение инлайн-методов в ф-ии %define-component-inline-methods.
16. Возврат созданного компонента.
-----------------------------

Продолжение следует ...