forked from pcl-ru/pcl-ru
-
Notifications
You must be signed in to change notification settings - Fork 0
/
chapter-15.tex
487 lines (411 loc) · 38.2 KB
/
chapter-15.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
\chapter{Практика: переносимая библиотека файловых путей}
\label{ch:15}
\thispagestyle{empty}
Как было сказано в предыдущей главе, Common Lisp предоставляет абстракцию файловых путей,
и предполагается, что она изолирует вас от деталей того, как различные операционные и
файловые системы именуют файлы. Файловые пути предоставляют удобное API для управления
именами самими по себе, но когда дело доходит до функций, которые на самом деле
взаимодействуют с файловой системой, всё не так гладко.
Корень проблемы, как я упомянул, в том, что абстракция файлового пути была спроектирована
для представления файловых имён среди много большего многообразия файловых систем, чем
используется в данный момент. К несчастью, сделав файловые пути достаточно абстрактными,
чтобы учитывать большое разнообразие файловых систем, создатели Common Lisp оставили
разработчикам реализаций на выбор многочисленные варианты, как точно отображать абстракцию
файлового пути в любую конкретную файловую систему. Следовательно, различные разработчики
реализаций, каждые из которых реализуют абстракцию файлового пути для одной и той же
файловой системы, сделав разный выбор в нескольких ключевых точках, могут закончить с
соответствующими стандарту реализациями, которые тем не менее будут демонстрировать
различное поведение для нескольких основных функций, связанных с файловыми путями.
Однако так или иначе все реализации обеспечивают одну и ту же базовую функ\-цио\-наль\-ность,
так что не так сложно написать библиотеку, которая предоставляет единообразный интерфейс
для обычных операций в разных реализациях. Это и будет нашей задачей в данной главе.
В~добавление к предоставлению вам нескольких полезных функций, которые вы будете
использовать в будущих главах, написание этой библиотеки даст вам возможность научиться
писать код, имеющий дело с различиями в реализациях.
\section{API}
Базовые операции, которые будет поддерживать библиотека,~-- получение списка файлов в
директории и проверка существования в данной директории файла с данным именем. Вы также
напишете функцию для рекурсивного прохода по иерархии директорий с вызовом заданной
функции для каждого файлового пути в дереве.
Теоретически эти операции просмотра директории и проверки существования файла уже
предоставлены стандартными функциями \lstinline{DIRECTORY} и \lstinline{PROBE-FILE}. Однако вы
увидите, что есть несколько разных путей для реализации этих функций~-- все в рамках
правильных интерпретаций стандарта языка,~-- и вам захочется написать новые функции,
которые предоставят единообразное поведение для разных реализаций.
\section{Переменная *FEATURES* и обработка условий при считывании}
Перед тем как реализовать API в библиотеке, которая будет корректно работать на
нескольких реализациях Common Lisp, мне нужно показать вам механизм для напи\-са\-ния кода,
предназначенного для определённой реализации.
В~то время как большая часть кода, которую вы будете писать, будет <<переносимой>> в том
смысле, что она будет выполняться одинаково на любой реализации, соответствующей стандарту
Common Lisp, вам может внезапно понадобиться положиться на функциональность, специфичную
для реализации, или написать немного разные куски кода для различных реализаций. Чтобы
помочь вам сделать это без полного разрушения переносимости вашего кода, Common Lisp
предоставляет механизм, называемый \textit{обработка условий при считывании}, который
позволит вам включать код при определённых условиях, основанных на таких особенностях, как
реализация, в которой идёт выполнение.
Этот механизм состоит из переменной \lstinline{*FEATURES*} и двух дополнительных частей
синтаксиса, понимаемых процедурой чтения Lisp. \lstinline{*FEATURES*} является списком символов;
каждый символ представляет собой <<свойство>>, которое присутствует в реализации или
используемой ей платформе. Эти символы затем используются в выражениях на свойства,
которые вычисляются как истина или ложь, в зависимости о того, присутствуют ли символы из
этих выражений в переменной \lstinline{*FEATURES*}. Простейшее выражение на свойство~--
одиночный символ; это выражение истинно, если символ входит в \lstinline{*FEATURES*}, и ложно в
противном случае. Другие выражения на свойства~-- логические выражения, построенные из
операторов \lstinline{NOT}, \lstinline{AND} или \lstinline{OR}. Например, если бы вы захотели установить
условие, чтобы некоторый код был включён, только если присутствуют свойства foo и bar, вы
могли бы записать выражение на свойства \lstinline{(and foo bar)}.
Считыватель использует выражения на свойства вместе с двумя элементами синтаксиса,
\lstinline!#+! и~\lstinline!#-!. Когда процедура чтения видит один из них, он сначала читает
выражение на свойство и затем вычисляет его, как я только что описал. Когда выражение на
свойство, следующее за \lstinline!#+!, является истинным, процедура чтения считывает следующее
выражение обычным образом. В~противном случае он пропускает следующее выражение, считая
его пробелом. \lstinline!#-! работает также, за исключением того, что форма считывается,
если выражение на свойство ложно, и пропускается, если оно истинно.
Начальное значение \lstinline{*FEATURES*} зависит от реализации, и функциональность,
подразумеваемая любым присутствующим в ней символом, тоже определяется реализацией. Однако
все реализации определяют, по крайней мере, один символ, указывающий на данную реализацию.
Например, Allegro Common Lisp определяет символ \lstinline{:allegro}, CLISP определяет
\lstinline{:clisp}, SBCL определяет \lstinline{:sbcl}, и CMUCL определяет
\lstinline{:cmu}. Чтобы избежать зависимостей от пакетов, которые могут или не могут
существовать в различных реализациях, символы в \lstinline{*FEATURES*}~-- обычно ключевые
слова, и процедура чтения связывает \lstinline{*PACKAGE*} c пакетом \lstinline{KEYWORD} во
время считывания выражений. Таким образом, имя без указания пакета будет прочитано как
ключевой символ. Итак, вы могли бы написать функцию, которая ведёт себя немного по-разному
в каждой из только что упомянутых реализаций:
\begin{myverb}
(defun foo ()
#+allegro (do-one-thing)
#+sbcl (do-another-thing)
#+clisp (something-else)
#+cmu (yet-another-version)
#-(or allegro sbcl clisp cmu) (error "Not implemented"))
\end{myverb}
\noindent{}В~Allegro этот код будет считан, как если бы он был написан так:
\begin{myverb}
(defun foo ()
(do-one-thing))
\end{myverb}
\noindent{}тогда как в SBCL процедура чтения прочитает следующее:
\begin{myverb}
(defun foo ()
(do-another-thing))
\end{myverb}
\noindent{}а в реализации, отличной от тех, на которые специально установлены условия, будет считано
следующее:
\begin{myverb}
(defun foo ()
(error "Not implemented"))
\end{myverb}
Так как обработка условий происходит в процедуре чтения, компилятор даже не увидит пропущенных
выражений\footnote{Одним слегка назойливым последствием того, как работает обработка
условий при чтении, является то, что непросто написать проваливающийся case. Например,
если вы добавите в \lstinline{foo} поддержку новой реализации, добавив ещё одно выражение,
охраняемое \lstinline!#+!, вам придётся помнить о том, что нужно добавить то же самое
свойство или выражение со свойством после \lstinline!#-!, или будет вычислена форма с
ERROR, когда будет запущен ваш новый код.}\hspace{\footnotenegspace}. Это означает, что вы не тратите время
выполнения на наличие различных версий для разных реализаций. Также когда процедура чтения
пропускает выражения, на которые установлены условия, его не волнуют входящие символы, так
что пропущенные выражения могут спокойно содержать символы из пакетов, которые не
существуют в других реализациях.
\section{Получение списка файлов в директории}
Вы можете реализовать функцию для получения списка файлов одной директории,
\lstinline{list-directory}, как тонкую обёртку вокруг стандартной функции
\lstinline{DIRECTORY}. \lstinline{DIRECTORY} принимает особый тип файлового пути,
называемого \textit{шаблоном файлового пути}, который имеет одну или более компоненту,
содержащую специальное значение \lstinline{:wild}, и возвращает список файловых путей,
представляющих файлы в файловой системе, которые соответствуют шаблону\footnote{Другое
специальное значение, \lstinline{:wild-inferiors}, может появляться как часть компоненты
директории шаблона файлового пути, но в данной главе это не понадобится.}\hspace{\footnotenegspace}. Алгоритм
сопоставления~-- как большинство вещей которым приходится иметь дело с взаимодействием
между Lisp и конкретной файловой системой,~-- не определяется стандартом языка, но
большинство реализаций на Unix и Windows следует одной и той же базовой схеме.
\vspace{0.8cm}
\begin{lrbox}{\chonefiveone}
\begin{minipage}{\linewidth}
\begin{myverb}
(in-package :cl-user)
(defpackage :com.gigamonkeys.pathnames
(:use :common-lisp)
(:export
:list-directory
:file-exists-p
:directory-pathname-p
:file-pathname-p
:pathname-as-directory
:pathname-as-file
:walk-directory
:directory-p
:file-p))
\end{myverb}
\end{minipage}
\end{lrbox}
\textintable{Создание пакета библиотеки}{Кстати, о пакетах: если вы загрузите полный код этой библиотеки, то увидите, что она
определена в новом пакете, \lstinline{com.gigamonkeys.pathnames}. Я расскажу о деталях
определения и использования пакетов в главе~\ref{ch:21}. Сейчас вы должны отметить, что некоторые
реализации предоставляют свои пакеты, которые содержат функции с некоторыми такими же
именами, как вы определите в этой главе, и делают эти имена доступными из пакета
\lstinline{CL-USER}. Таким образом, если вы попытаетесь определить функции этой библиотеки,
находясь в пакете \lstinline{CL-USER}, вы можете получить сообщения об ошибках о конфликтах с
существующими определениями. Чтобы избежать этой возможности, вы можете создать файл с
названием \lstinline{packages.lisp} и следующим содержанием:\\[-3pt]
\noindent{}\usebox{\chonefiveone}\\
\noindent{}и сделать \lstinline{LOAD} на него. Тогда в REPL или в начале файла, в который вы
печатаете определения из этой главы, напечатайте следующее выражение:
\begin{myverb}
(in-package :com.gigamonkeys.pathnames)
\end{myverb}
В~дополнение к избежанию конфликтов имён с символами, уже доступными в \lstinline{CL-USER},
создание пакета для библиотеки таким образом также сделает проще использование её в другом
коде, как вы увидите из нескольких будущих глав.
}
Функция \lstinline{DIRECTORY} имеет две проблемы, с которыми придётся иметь дело
\lstinline{list-directory}. Главная проблема состоит в том, что определённые аспекты поведения
этой функции различаются достаточно сильно для различных реализаций Common Lisp, даже для
одной и той же операционной системы. Другая проблема в том, что, хотя \lstinline{DIRECTORY} и
предоставляет мощный интерфейс для получения списка файлов, её правильное использование
требует понимания некоторых достаточно тонких моментов в абстракции файловых путей. С
этими тонкостями и стилевыми особенностями различных реализаций само написание
переносимого кода, использующего \lstinline{DIRECTORY} для таких простых вещей, как получение
списка всех файлов и поддиректорий для единственной директории, могло бы стать
разочаровывающим опытом. Вы можете разобраться со всеми тонкостями и характерными
особенностями раз и навсегда, написав \lstinline{list-directory} и забыв о них после этого.
Одна тонкость, которая обсуждалась в главе~\ref{ch:14},~-- это два способа представлять
имя директории в виде файлового пути: в форме директории и в форме файла.
Чтобы \lstinline{DIRECTORY} возвратила вам список файлов в \lstinline{/home/peter/}, вам надо
передать ей шаблон файлового пути, чья компонента директории~-- это директория, которую
вы хотите прочитать, и чьи компоненты имени и типа являются \lstinline{:wild}. Таким образом,
может показаться, что для получения списка файлов в \lstinline{/home/peter/} вы можете написать
это:
\begin{myverb}
(directory (make-pathname :name :wild :type :wild :defaults home-dir))
\end{myverb}
\noindent{}где \lstinline{home-dir} является файловым путём, представляющим \lstinline{/home/peter/}. Это бы
сработало, если бы \lstinline{home-dir} была бы в форме директории. Но если бы она была в
файловой форме~-- например, если бы она была создана разбором строки
\lstinline{"/home/peter"},~-- тогда бы это выражение вернуло список всех файлов в
\lstinline{/home}, так как компонента имени \lstinline{"peter"} была бы заменена на
\lstinline{:wild}.
Чтобы избежать беспокойства о явном преобразовании между представлениями, вы можете
определить \lstinline{list-directory} так, чтобы она принимала нешаблонный файловый путь в
обоих формах, который затем она будет переводить в подходящий шаблон файлового пути.
Чтобы облегчить это, вам следует определить несколько вспомогательных функций. Одна,
\lstinline{component-present-p}, будет проверять, <<существует>> ли данная компонента в
файловом пути, имея в виду не \lstinline{NIL} и не специальное значение
\lstinline{:unspecific}.\footnote{Реализации могут возвращать \lstinline{:unspecific}
вместо \lstinline{NIL} как значение компоненты файлового пути в определённых ситуациях,
например когда эта компонента не используется в этой реализации.}\hspace{\footnotenegspace}. Другая,
\lstinline{directory-pathname-p}, проверяет, задан ли файловый путь уже в форме
директории, и \mbox{третья}, \lstinline{pathname-as-directory}, преобразует любой файловый
путь в файловый путь в форме директории.
\begin{myverb}
(defun component-present-p (value)
(and value (not (eql value :unspecific))))
(defun directory-pathname-p (p)
(and
(not (component-present-p (pathname-name p)))
(not (component-present-p (pathname-type p)))
p))
(defun pathname-as-directory (name)
(let ((pathname (pathname name)))
(when (wild-pathname-p pathname)
(error "Can't reliably convert wild pathnames."))
(if (not (directory-pathname-p name))
(make-pathname
:directory (append (or (pathname-directory pathname) (list :relative))
(list (file-namestring pathname)))
:name nil
:type nil
:defaults pathname)
pathname)))
\end{myverb}
Теперь кажется, что можно создать шаблон файлового пути для передачи \lstinline{DIRECTORY},
вызвав \lstinline{MAKE-PATHNAME} с формой директории, возвращённой
\lstinline{pathname-as-directory}. К несчастью, благодаря одной причуде в реализации
\lstinline{DIRECTORY} в CLISP всё не так просто. В~CLISP \lstinline{DIRECTORY} вернёт файлы без
расширений, только если компонента типа шаблона является \lstinline{NIL}, но не
\lstinline{:wild}. Так что вы можете определить функцию \lstinline{directory-wildcard}, которая
принимает файловый путь в форме директории или файла и возвращает шаблон, подходящий для
данной реализации, используя проверку условий при считывании, для того чтобы делать
файловый путь с компонентой типа \lstinline{:wild} во всех реализациях, за исключением CLISP, и
\lstinline{NIL} в CLISP.
\begin{myverb}
(defun directory-wildcard (dirname)
(make-pathname
:name :wild
:type #-clisp :wild #+clisp nil
:defaults (pathname-as-directory dirname)))
\end{myverb}
Заметьте, что каждое условие при считывании работает на уровне единственного выражения
после \lstinline!#-clisp!, выражение \lstinline{:wild} будет или считано, или пропущено; ровно
как и после \lstinline!#+clisp!, \lstinline{NIL} будет прочитано или пропущено.
Теперь вы можете первый раз вгрызться в функцию \lstinline{list-directory}.
\begin{myverb}
(defun list-directory (dirname)
(when (wild-pathname-p dirname)
(error "Can only list concrete directory names."))
(directory (directory-wildcard dirname)))
\end{myverb}
Утверждается, что эта функция будет работать в SBCL, CMUCL и LispWorks. К~несчастью,
остаётся парочка различий, которые надо сгладить. Одно отличие состоит в том, что не все
реализации вернут поддиректории данной директории. Allegro, SBCL, CMUCL и LispWorks
сделают это. OpenMCL не делает это по умолчанию, но сделает, если вы передадите
\lstinline{DIRECTORY} истинное значение по специфичному для этой реализации ключевому аргументу
\lstinline{:directories}. \lstinline{DIRECTORY} в CLISP возвращает поддиректории, только когда ей
передаётся шаблон файлового пути с \lstinline{:wild} в последнем элементе компоненты директории
и \lstinline{NIL} в компонентах имени и типа. В~этом случае он вернёт только поддиректории,
так что вам придётся вызвать \lstinline{DIRECTORY} дважды с разными шаблонами и скомбинировать
результаты.
Как только вы заставите все реализации возвращать директории, вы узнаете, что они также
различаются в том, возвращают ли они имена директорий в форме директорий или файлов. Вы
хотите, чтобы \lstinline{list-directory} всегда возвращала имена директорий в форме директорий,
так чтобы вы могли отличать поддиректории от обычных файлов, основываясь просто на
имени. За исключением Allegro, все реализации этой библиотеки поддерживают это. Allegro,
c другой стороны, требует передачи \lstinline{DIRECTORY} характерного для этой реализации
аргумента \lstinline{:directories-are-files} со значением \lstinline{NIL}, чтобы заставить её
возвратить директории в форме файлов.
Как только вы узнали о том, как сделать так, чтобы каждая реализация делала то, что вы
хотите, само написание \lstinline{list-directory} становится просто делом сочетания различных
версий при помощи проверки условий при чтении.
\begin{myverb}
(defun list-directory (dirname)
(when (wild-pathname-p dirname)
(error "Can only list concrete directory names."))
(let ((wildcard (directory-wildcard dirname)))
#+(or sbcl cmu lispworks)
(directory wildcard)
#+openmcl
(directory wildcard :directories t)
#+allegro
(directory wildcard :directories-are-files nil)
#+clisp
(nconc
(directory wildcard)
(directory (clisp-subdirectories-wildcard wildcard)))
#-(or sbcl cmu lispworks openmcl allegro clisp)
(error "list-directory not implemented")))
\end{myverb}
Функция \lstinline{clisp-subdirectories-wildcard} на самом деле не является присущей CLISP, но
так как она не нужна никакой другой реализации, вы можете ограничить её условием при
чтении. В~этом случае, поскольку выражение, следующее за \lstinline!#+!, является целым
\lstinline{DEFUN}, будет или не будет включено всё определение функции, в зависимости от того,
присутствует ли clisp в \lstinline{*FEATURES*}.
\begin{myverb}
#+clisp
(defun clisp-subdirectories-wildcard (wildcard)
(make-pathname
:directory (append (pathname-directory wildcard) (list :wild))
:name nil
:type nil
:defaults wildcard))
\end{myverb}
\section{Проверка существования файла}
Чтобы заменить \lstinline{PROBE-FILE}, вы можете определить функцию с именем
\lstinline{file-exists-p}. Она должна принимать имя файла и, если файл существует, возвращать
то же самое имя и \lstinline{NIL}, если не существует. Она должна быть способна принимать имя
директории и в виде директории, и в виде файла, но должна всегда возвращать файловый путь
в форме директории, если файл существует и является директорией. Это позволит вам
использовать \lstinline{file-exists-p} вместе с \lstinline{directory-pathname-p}, чтобы проверить,
является ли данное имя именем файла или директории.
Теоретически \lstinline{file-exists-p} достаточно похожа на стандартную функцию
\lstinline{PROBE-FILE}, и на самом деле в нескольких реализациях~-- SBCL, LispWorks, OpenMCL
– \lstinline{PROBE-FILE} уже даёт вам то поведение, которого вы хотите от
\lstinline{file-exists-p}. Но не все реализации \lstinline{PROBE-FILE} ведут себя так.
Функции \lstinline{PROBE-FILE} в Allegro и CMUCL близки к тому, чего вы хотите,~-- они
принимают имя директории в обеих формах, но вместо возвращения имени в форме директории
просто возвращают его в той же самой форме, в которой им был передан аргумент. К счастью,
если им передаётся имя недиректории в форме директории, они возвращают \lstinline{NIL}. Так
что в этих реализациях вы можете получить желаемое поведение, сначала передав
\lstinline{PROBE-FILE} имя в форме директории,~-- если файл существует и является директорией,
она возвратит имя в форме директории. Если этот вызов вернёт \lstinline{NIL}, вы попытаетесь
снова с именем в форме файла.
CLISP, с другой стороны, снова делает это по-своему. Его \lstinline{PROBE-FILE} немедленно
сигнализирует ошибку, если передано имя в форме директории, вне зависимости от того,
существует ли файл или директория с таким именем. Она также сигнализирует ошибку, если в
файловой форме передано имя, которое на самом деле является именем директории. Для
определения, существует ли директория, CLISP предоставляет собственную функцию:
\lstinline{probe-directory} (в пакете \lstinline{ext}). Она практически является зеркальным
отражением \lstinline{PROBE-FILE}: выдаёт ошибку, если ей передаётся имя в файловой форме или
если передано имя в форме директории, которое оказалось именем файла. Единственное
различие в том, что она возвращает \lstinline{T}, а не файловый путь, когда существует
названная директория.
Но даже в CLISP вы можете реализовать желаемую семантику, обернув вызовы \lstinline{PROBE-FILE}
и \lstinline{probe-directory} в \lstinline{IGNORE-ERRORS}\footnote{Это немного неправильно в том
смысле, что если \lstinline{PROBE-FILE} сигнализирует ошибку по другой причине, данный код
будет работать неправильно. К несчастью, документация CLISP не указывает, какие ошибки
могут сигнализировать \lstinline{PROBE-FILE} и \lstinline{probe-directory}, и эксперимент
показывает, что они сигнализируют \lstinline{simple-file-errors} в большинстве ошибочных
ситуаций.}\hspace{\footnotenegspace}.
\begin{myverb}
(defun file-exists-p (pathname)
#+(or sbcl lispworks openmcl)
(probe-file pathname)
#+(or allegro cmu)
(or (probe-file (pathname-as-directory pathname))
(probe-file pathname))
#+clisp
(or (ignore-errors
(probe-file (pathname-as-file pathname)))
(ignore-errors
(let ((directory-form (pathname-as-directory pathname)))
(when (ext:probe-directory directory-form)
directory-form))))
#-(or sbcl cmu lispworks openmcl allegro clisp)
(error "file-exists-p not implemented"))
\end{myverb}
Функция \lstinline{pathname-as-file}, которая нужна вам для реализации \lstinline{file-exists-p} в
CLISP, является обратной для определённой ранее \lstinline{pathname-as-directory}, возвращающей
файловый путь, являющийся эквивалентом аргумента в файловой форме. Несмотря на то что эта
функция нужна здесь только для CLISP, она полезна в общем случае, так что определим её для
всех реализаций и сделаем частью библиотеки.
\begin{myverb}
(defun pathname-as-file (name)
(let ((pathname (pathname name)))
(when (wild-pathname-p pathname)
(error "Can't reliably convert wild pathnames."))
(if (directory-pathname-p name)
(let* ((directory (pathname-directory pathname))
(name-and-type (pathname (first (last directory)))))
(make-pathname
:directory (butlast directory)
:name (pathname-name name-and-type)
:type (pathname-type name-and-type)
:defaults pathname))
pathname)))
\end{myverb}
\section{Проход по дереву каталогов}
Наконец, чтобы завершить библиотеку, вы можете реализовать функцию, называемую
\lstinline{walk-directory}. В~отличие от ранее определённых функций, эта функция не нужна для
сглаживания различий между реализациями; она просто использует функции, которые вы уже
определили. Однако она довольно удобна, и вы будете её несколько раз использовать в
последующих частях. Она будет принимать имя директории и функцию и вызывать функцию на
всех файлах, входящих в директорию рекурсивно. Она также принимает два ключевых аргумента:
\lstinline{:directories} и \lstinline{:test}. Когда \lstinline{:directories} истинно, она будет вызывать
функцию на именах директорий, как на обычных файлах. Аргумент \lstinline{:test}, если
предоставлен, определяет другую функцию, которая вызывается на каждом файловом пути до
того, как будет вызвана главная функция, которая будет вызвана, только если тестовая
функция возвратит истинное значение.
\begin{myverb}
(defun walk-directory (dirname fn &key directories (test (constantly t)))
(labels
((walk (name)
(cond
((directory-pathname-p name)
(when (and directories (funcall test name))
(funcall fn name))
(dolist (x (list-directory name)) (walk x)))
((funcall test name) (funcall fn name)))))
(walk (pathname-as-directory dirname))))
\end{myverb}
Теперь у вас есть полезная библиотека функций для работы с файловыми путями. Как я
упомянул, эти функции окажутся полезны в следующих главах, особенно в главах~\ref{ch:23}
и~\ref{ch:27}, где вы будете использовать \lstinline{walk-directory}, чтобы пройтись по
дереву директорий, содержащих спамерские сообщения и MP3-файлы. Но до того, как мы
доберёмся до этого, мне тем не менее нужно поговорить об объектной ориентации, теме
следующих двух глав.
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "pcl-ru"
%%% TeX-open-quote: "<<"
%%% TeX-close-quote: ">>"
%%% End: