среда, 31 марта 2010 г.

Установка Subversion

Поставил Subversion по вот этому HowTo:

http://habrahabr.ru/blogs/ubuntu/26117/

Ниже он же с моими комментариями.

Установка Subversion и Trac

1. Ставим trac, python и subversion:

sudo apt-get install trac libapache2-svn subversion python-subversion libapache2-mod-python

2. Включаем модуль питона:

sudo a2enmod mod_python

Настройка Subversion

1. Создаем группу для работы с SVN:

sudo groupadd svn

2. Добавляем себя (ваше имя пользователя в Ubuntu) в группу svn:

sudo usermod -a -G svn имя_пользователя

3. Добавляем apache в группу svn:

sudo usermod -a -G svn www-data

4. Создаем папку для будущего репозитория:

sudo mkdir /var/svn

5. Создаем репозиторий:

sudo svnadmin create /var/svn

6. Меняем права к папке для доступа пользователям из группы svn:

sudo chown -R www-data:svn /var/svn

7. Разрешаем на запись группе и владельцу:

sudo chmod -R g+ws /var/svn

8. Создаем пароль для доступа к папке с репозиторием, который впоследствии будет использоваться apache'ем:

sudo htpasswd -c -m /etc/apache2/svn.htpasswd имя_пользователя

Примечание Hilo: это отдельный пароль для svn, с паролем входа в систему совпадать не обязан. При создании пароля для следующего пользователя надо запускать htpasswd уже без ключа -c, который означает "создать новый файл, стерев, что как, предыдущий".

9. Теперь создадим правило для Apache для доступа к svn-репозиторию:

Alt+F2 gksu gedit /etc/apache2/conf.d/svn

<Location "/svn">
DAV svn
SVNPath /var/svn
AuthType Basic
AuthName "SVN Repo"
AuthUserFile /etc/apache2/svn.htpasswd
Require valid-user
</Location>

Примечание Hilo: я вместо этого добавил следующее в /etc/apache2/sites-available/default:

# 
# Subversion
#
Listen 82
ServerName *:82
NameVirtualHost *:82
<VirtualHost *:82>
<Location "/">
DAV svn
SVNPath /var/svn
AuthType Basic
AuthName "SVN Repo"
AuthUserFile /etc/apache2/svn.htpasswd
Require valid-user
</Location>
</VirtualHost>


Идея в том, чтобы svn был доступен по localhost:82


10. SVN установлен! Он доступен по адресу http://localhost/svn

Примечание Hilo: sudo /etc/init.d/apache2 restart

Настройка Trac

1. Создаем папку для Trac'а :

sudo mkdir /var/trac

2. Создаем среду для работы Trac с SVN :

sudo trac-admin /var/trac initenv

3. Меняем права к папке с trac'ом :

sudo chown -R www-data:svn /var/trac
sudo chmod -R g+ws /var/trac

4. Создаем пароль для админ-доступа к папке с trac'ом, который впоследствии будет использоваться apache'ем :

sudo htpasswd -c -m /etc/apache2/trac.htpasswd имя_пользователя

5. Создадим правило для Apache для доступа к trac'у :

Alt+F2 gksu gedit /etc/apache2/conf.d/trac

<LocationMatch "/trac/login">
AuthType Basic
AuthName "Projects"
AuthUserFile /etc/apache2/trac.htpasswd
Require valid-user
</LocationMatch>

<Location /trac>
SetHandler mod_python
PythonInterpreter main_interpreter
PythonHandler trac.web.modpython_frontend
PythonOption TracEnv /var/trac
PythonOption TracUriRoot /trac
</Location>

Примечание Hilo: то же самое, повесил на порт 83, добавив в
/etc/apache2/sites-available/default вот что:

#
# Trac
#
Listen 83
ServerName *:83
NameVirtualHost *:83
<VirtualHost *:83>

<LocationMatch "/login">
AuthType Basic
AuthName "Projects"
AuthUserFile /etc/apache2/trac.htpasswd
Require valid-user
</LocationMatch>

<Location "/">
SetHandler mod_python
PythonInterpreter main_interpreter
PythonHandler trac.web.modpython_frontend
PythonOption TracEnv /var/trac
PythonOption TracUriRoot /
</Location>

</VirtualHost>


6. Trac установлен! Он доступен по адресу http://localhost/trac

Установка плагина TracWebAdmin

1. Чтобы устанавливать плагины для trac'а - нам нужен пакет "python-setuptools" :
sudo apt-get install python-setuptools

2. Переходим в папку, где лежат trac-плагины :
cd /usr/share/trac/plugins

3. Будем ставить TracWebAdmin из svn-репозитария :
sudo svn co http://svn.edgewall.org/repos/trac/sandbox/webadmin/

Примечание Hilo: после выполнения команды скачался один файл README, в котором
было предложено сделать switch. Сработало следующее:

cd webadmin
sudo svn switch http://svn.edgewall.org/repos/trac/plugins/0.10/webadmin


4. Переходим в папку с webadmin'ом и собираем его установочный egg-файл :

cd webadmin
sudo python setup.py bdist_egg

5. Переходим в папку со свежеиспеченным установщиком командой...

cd /usr/share/trac/plugins/webadmin/dist
...выводим создержимое папки...
dir
... и копируем имя egg-файла.

6. Устанавливаем TracWebAdmin :

sudo easy_install-2.5 имя_egg_файла

7. Включаем наш плагин в конфигурацию trac'а :

Alt+F2 gksu gedit /etc/trac/trac.ini
[components]
webadmin.* = enabled

Подсчитать число строк в проекте

Как подсчитать общее число строк во всех скриптах проекта?

Например, так:

find . -name "*cgi" | xargs wc -l


Правда, я делал чуть иначе. Сначала я вывел список всех файлов в файл:

find . -regex ".*\(lsp\|cgi\|scm\)$" > test.txt


Потом убрал в файле лишнее (типа резервных копий) текстовым редактором. Потом уже направил оставшиеся файлы на wc:

cat test.txt > xargs wc -l

вторник, 30 марта 2010 г.

PHP: парсинг лога

Базовый инструментарий для парсинга логов в PHP практически совпадает с ЛИСП’овским:

explode — разбить строку в массив
preg_match_all — найти все вхождения (в массив)
count — длина массива
array_unique — уникальные элементы массива
file — прочесть файл в массив
file_get_contents — прочесть файл в строку

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

воскресенье, 28 марта 2010 г.

Ubuntu — смонтировать флешку

Ситуация: на удалённом компьютере вставлена, но не смонтирована флешка. Ребут не помогает, так как флешка была отключена через «отсоединить том» на рабочем столе Гнома.

Решение:

sudo mkdir /media/flash
sudo mount /dev/sdb1 /media/flash -o utf8

суббота, 27 марта 2010 г.

Большие числа

Сделал мини-библиотеку для работы с большими числами (типа стозначных или пятисотзначных).

Странное дело: первое возведение тройки в тысячную степень занимает 1 секунду, а каждое следующее возведение увеличивает это время примерно на секунду, пока к пятой итерации не достигает 4,5 секунд. На этой скорости происходит стабилизация: сотое повторение занимает те же 4,5 секунды.

Подозреваю, проблема в том, что списки длиной более 100 элементов работают медленнее, чем массивы. Впрочем, так как стабилизация таки происходит, проблема некритична. Учитывая, что возведение тройки в десятитысячную степень занимает уже пять минут, следует предположить, что я отловил грань, за которой длина списка становится критичной.

; Добить нулями num до длины len
(define (big-leading-zero len num)
(append (dup 0 (- len (length num))) num))

; Выровнять большие числа по длине
(define (big-align)
(let (len (apply max (map length (args))))
(map (curry big-leading-zero len) (args))))

; 123 -> '(1 2 3)
(define (int-to-big num)
(map int (explode (string num))))

; '(1 2 3) -> 123
(define (big-to-int big)
(int (join (apply string big))))

; '(1 2 3) -> "123"
(define (big-to-string big)
(join (map string big)))

; "123" -> '(1 2 3)
(define (string-to-big big)
(map int (explode big)))

; "Упаковать" число так, чтобы в каждом разряде сидело не больше 9
(define (big-pack num)
(let (num (reverse num) o-num '() dig nil tra 0 lead nil)
(while (or (> tra) (not (null? num)))
(set 'dig (+ (or (pop num) 0) tra))
(push (% dig 10) o-num)
(set 'tra (/ dig 10)))
(if (set 'lead (find 0 o-num <)) (lead o-num) '(0))))

; Сложить большие числа
(define (big-add)
(big-pack (map (fn (x) (apply + x)) (transpose (apply big-align (args))))))

; Умножить два больших числа
(define (big-mul-2 num1 num2)
(apply big-add
(map (fn (y) (append y (dup 0 $idx)))
(reverse (map (fn (x) (map (curry mul x) num2)) num1)))))

; Перемножить большие числа
(define (big-mul)
(apply big-mul-2 (args) 2))

; Возвести большое число в большую степень
(define (big-pow num pw)
(let (pw2 (list num))
(dotimes (x (- (length (bits pw)) 1))
(push (apply big-mul-2 (dup (if pw2 (first pw2) num) 2)) pw2))
(apply big-mul
(map (fn (x y) (if (= x "1") y '(1))) (explode (bits pw)) pw2))))


А вот довесочек, проверяющий скорость:

; Проверка скорости
(dotimes (x 1000)
(println x ": " (time (big-pow '(3) 1000))))

russian.lsp

Обновил модуль "russian.lsp".

1. Нашёл баг в функции cyr-translit: "explode" неправильно работал из-под Windows с русскими буквами. Заменил конструкцию (explode linea) на (cyr-explode linea) и всё заработало. В функции cyr-explode пришлось выяснять текущую кодировку и использовать для препарирования строки или explode или find-all ".", смотря какой из вариантов работает.

2. Нашёл мелкий баг в функции cyr-utf-win: в конце строки возвращался (char nil), то есть, "\000". На печати он был не виден, поэтому баг я раньше не замечал.

; Russian encoding support by Anon. 27 Mar 2010.
; Varning: this module and codepages inside are far not complete. Some special
; symbols are not included.
;
; Functions:
;
; Usage: (cyr-win-utf "text in windows-1251 encoding")
; Decodes text from windows-1251 to utf-8
;
; Usage: (cyr-koi-utf "text in KOI8R encoding")
; Decodes text from KOI8R to utf-8
;
; Usage: (cyr-utf-win "text in utf-8 encoding")
; Decodes text from utf-8 to win-1251
;
; Usage: (url-encode-utf "В. Пупкин")
; Encodes strings for urls (utf)
; Output: "%D0%92.%20%D0%9F%D1%83%D0%BF%D0%BA%D0%B8%D0%BD"
;
; Usage: (url-encode-win "Василий Пупкин")
; Encodes strings for urls (windows-1251)
; Output: "%C2.%20%CF%F3%EF%EA%E8%ED"
;
; Usage: (cyr-rtf "\\u1055\\'3F\\u1086\\'3F\\u1089\\'3F\\u1083\\'3F")
; Decodes russian letters sequences in the .rtf file.
; Example: (cyr-rtf (read-file "report.rtf"))
;
; Usage: (cyr-rtf-r "спецификация")
; Encodes a string to the rtf russian letters format (useful for searching
; in raw-rtf). Expect problems with spaces and other \u special symbols.
;
; Usage: (cyr-translit "Щёлочь, кислота, алюминий") => "SCHyolochy, kislota, alyuminiy"
;
; Usage: (cyr-translit_ "Василий Пупкин") => "Vasiliy_Pupkin"

; Russian alphabet (33 small and 33 big letters)
(set 'cyr-alphabet (list "а" "б" "в" "г" "д" "е" "ё" "ж" "з" "и" "й" "к" "л" "м" "н" "о" "п" "р" "с" "т" "у" "ф" "х" "ц" "ч" "ш" "щ" "ъ" "ы" "ь" "э" "ю" "я" "А" "Б" "В" "Г" "Д" "Е" "Ё" "Ж" "З" "И" "Й" "К" "Л" "М" "Н" "О" "П" "Р" "С" "Т" "У" "Ф" "Х" "Ц" "Ч" "Ш" "Щ" "Ъ" "Ы" "Ь" "Э" "Ю" "Я"))

; Url-encoding for russian letters UTF
(set 'en-url-utf '(("а" "%D0%B0") ("б" "%D0%B1") ("в" "%D0%B2") ("г" "%D0%B3")
("д" "%D0%B4") ("е" "%D0%B5") ("ё" "%D1%91") ("ж" "%D0%B6") ("з" "%D0%B7")
("и" "%D0%B8") ("й" "%D0%B9") ("к" "%D0%BA") ("л" "%D0%BB") ("м" "%D0%BC")
("н" "%D0%BD") ("о" "%D0%BE") ("п" "%D0%BF") ("р" "%D1%80") ("с" "%D1%81")
("т" "%D1%82") ("у" "%D1%83") ("ф" "%D1%84") ("х" "%D1%85") ("ц" "%D1%86")
("ч" "%D1%87") ("ш" "%D1%88") ("щ" "%D1%89") ("ъ" "%D1%8A") ("ы" "%D1%8B")
("ь" "%D1%8C") ("э" "%D1%8D") ("ю" "%D1%8E") ("я" "%D1%8F") ("А" "%D0%90")
("Б" "%D0%91") ("В" "%D0%92") ("Г" "%D0%93") ("Д" "%D0%94") ("Е" "%D0%95")
("Ё" "%D0%81") ("Ж" "%D0%96") ("З" "%D0%97") ("И" "%D0%98") ("Й" "%D0%99")
("К" "%D0%9A") ("Л" "%D0%9B") ("М" "%D0%9C") ("Н" "%D0%9D") ("О" "%D0%9E")
("П" "%D0%9F") ("Р" "%D0%A0") ("С" "%D0%A1") ("Т" "%D0%A2") ("У" "%D0%A3")
("Ф" "%D0%A4") ("Х" "%D0%A5") ("Ц" "%D0%A6") ("Ч" "%D0%A7") ("Ш" "%D0%A8")
("Щ" "%D0%A9") ("Ъ" "%D0%AA") ("Ы" "%D0%AB") ("Ь" "%D0%AC") ("Э" "%D0%AD")
("Ю" "%D0%AE") ("Я" "%D0%AF")))

; Url-encoding for russian letters windows-1251
(set 'en-url-win '(("я" "%FF") ("ю" "%FE") ("э" "%FD") ("ь" "%FC") ("ы" "%FB")
("ъ" "%FA") ("щ" "%F9") ("ш" "%F8") ("ч" "%F7") ("ц" "%F6") ("х" "%F5")
("ф" "%F4") ("у" "%F3") ("т" "%F2") ("с" "%F1") ("р" "%F0") ("п" "%EF")
("о" "%EE") ("н" "%ED") ("м" "%EC") ("л" "%EB") ("к" "%EA") ("й" "%E9")
("и" "%E8") ("з" "%E7") ("ж" "%E6") ("ё" "%B8") ("е" "%E5") ("д" "%E4")
("г" "%E3") ("в" "%E2") ("б" "%E1") ("а" "%E0") ("Я" "%DF") ("Ю" "%DE")
("Э" "%DD") ("Ь" "%DC") ("Ы" "%DB") ("Ъ" "%DA") ("Щ" "%D9") ("Ш" "%D8")
("Ч" "%D7") ("Ц" "%D6") ("Х" "%D5") ("Ф" "%D4") ("У" "%D3") ("Т" "%D2")
("С" "%D1") ("Р" "%D0") ("П" "%CF") ("О" "%CE") ("Н" "%CD") ("М" "%CC")
("Л" "%CB") ("К" "%CA") ("Й" "%C9") ("И" "%C8") ("З" "%C7") ("Ж" "%C6")
("Ё" "%A8") ("Е" "%C5") ("Д" "%C4") ("Г" "%C3") ("В" "%C2") ("Б" "%C1")
("А" "%C0")))

; Url-encoding for special symbols
(set 'en-url-sym '((" " "%20") ("$" "%24") ("&" "%26") ("+" "%2B")
("," "%2C") ("/" "%2F") (":" "%3A") (";" "%3B") ("=" "%3D") ("?" "%3F") ("@" "%40")
(" " "%20") ("\"" "%22") ("<" "%3C") (">" "%3E") ("#" "%23") ("%" "%25") ("{" "%7B")
("}" "%7D") ("|" "%7C") ("\\" "%5C") ("^" "%5E") ("~" "%7E") ("[" "%5B") ("]" "%5D")
("`" "%60")))

; Encoding table utf-8
(set 'en-utf-8 '((53424 "а") (53425 "б") (53426 "в") (53427 "г") (53428 "д")
(53429 "е") (53649 "ё") (53430 "ж") (53431 "з") (53432 "и") (53433 "й")
(53434 "к") (53435 "л") (53436 "м") (53437 "н") (53438 "о") (53439 "п")
(53632 "р") (53633 "с") (53634 "т") (53635 "у") (53636 "ф") (53637 "х")
(53638 "ц") (53639 "ч") (53640 "ш") (53641 "щ") (53642 "ъ") (53643 "ы")
(53644 "ь") (53645 "э") (53646 "ю") (53647 "я") (53392 "А") (53393 "Б")
(53394 "В") (53395 "Г") (53396 "Д") (53397 "Е") (53377 "Ё") (53398 "Ж")
(53399 "З") (53400 "И") (53401 "Й") (53402 "К") (53403 "Л") (53404 "М")
(53405 "Н") (53406 "О") (53407 "П") (53408 "Р") (53409 "С") (53410 "Т")
(53411 "У") (53412 "Ф") (53413 "Х") (53414 "Ц") (53415 "Ч") (53416 "Ш")
(53417 "Щ") (53418 "Ъ") (53419 "Ы") (53420 "Ь") (53421 "Э") (53422 "Ю")
(53423 "Я") (14844052 "—") (14845078 "№")))

; Encoding table windows-1251
(set 'en-win-1251 '((255 "я") (254 "ю") (253 "э") (252 "ь") (251 "ы")
(250 "ъ") (249 "щ") (248 "ш") (247 "ч") (246 "ц") (245 "х") (244 "ф")
(243 "у") (242 "т") (241 "с") (240 "р") (239 "п") (238 "о") (237 "н")
(236 "м") (235 "л") (234 "к") (233 "й") (232 "и") (231 "з") (230 "ж")
(184 "ё") (229 "е") (228 "д") (227 "г") (226 "в") (225 "б") (224 "а")
(223 "Я") (222 "Ю") (221 "Э") (220 "Ь") (219 "Ы") (218 "Ъ") (217 "Щ")
(216 "Ш") (215 "Ч") (214 "Ц") (213 "Х") (212 "Ф") (211 "У") (210 "Т")
(209 "С") (208 "Р") (207 "П") (206 "О") (205 "Н") (204 "М") (203 "Л")
(202 "К") (201 "Й") (200 "И") (199 "З") (198 "Ж") (168 "Ё") (197 "Е")
(196 "Д") (195 "Г") (194 "В") (193 "Б") (192 "А") (185 "№")))

; Encoding table KOI8R
(set 'en-koi8r '((241 "Я") (224 "Ю") (252 "Э") (248 "Ь") (249 "Ы")
(255 "Ъ") (253 "Щ") (251 "Ш") (254 "Ч") (227 "Ц") (232 "Х") (230 "Ф")
(245 "У") (244 "Т") (243 "С") (242 "Р") (240 "П") (239 "О") (238 "Н")
(237 "М") (236 "Л") (235 "К") (234 "Й") (233 "И") (250 "З") (246 "Ж")
(179 "Ё") (229 "Е") (228 "Д") (231 "Г") (247 "В") (226 "Б") (225 "А")
(209 "я") (192 "ю") (220 "э") (216 "ь") (217 "ы") (223 "ъ") (221 "щ")
(219 "ш") (222 "ч") (195 "ц") (200 "х") (198 "ф") (213 "у") (212 "т")
(211 "с") (210 "р") (208 "п") (207 "о") (206 "н") (205 "м") (204 "л")
(203 "к") (202 "й") (201 "и") (218 "з") (214 "ж") (163 "ё") (197 "е")
(196 "д") (199 "г") (215 "в") (194 "б") (193 "а")))

; RTF-encoding (4-digit one). Warning: instead of "3f" sometimes you can see "3F".
(set 'en-rtf '(({\u1072\'3f} {а}) ({\u1073\'3f} {б}) ({\u1074\'3f} {в})
({\u1075\'3f} {г}) ({\u1076\'3f} {д}) ({\u1077\'3f} {е}) ({\u1105\'3f} {ё})
({\u1078\'3f} {ж}) ({\u1079\'3f} {з}) ({\u1080\'3f} {и}) ({\u1081\'3f} {й})
({\u1082\'3f} {к}) ({\u1083\'3f} {л}) ({\u1084\'3f} {м}) ({\u1085\'3f} {н})
({\u1086\'3f} {о}) ({\u1087\'3f} {п}) ({\u1088\'3f} {р}) ({\u1089\'3f} {с})
({\u1090\'3f} {т}) ({\u1091\'3f} {у}) ({\u1092\'3f} {ф}) ({\u1093\'3f} {х})
({\u1094\'3f} {ц}) ({\u1095\'3f} {ч}) ({\u1096\'3f} {ш}) ({\u1097\'3f} {щ})
({\u1098\'3f} {ъ}) ({\u1099\'3f} {ы}) ({\u1100\'3f} {ь}) ({\u1101\'3f} {э})
({\u1102\'3f} {ю}) ({\u1103\'3f} {я}) ({\u1040\'3f} {А}) ({\u1041\'3f} {Б})
({\u1042\'3f} {В}) ({\u1043\'3f} {Г}) ({\u1044\'3f} {Д}) ({\u1045\'3f} {Е})
({\u1025\'3f} {Ё}) ({\u1046\'3f} {Ж}) ({\u1047\'3f} {З}) ({\u1048\'3f} {И})
({\u1049\'3f} {Й}) ({\u1050\'3f} {К}) ({\u1051\'3f} {Л}) ({\u1052\'3f} {М})
({\u1053\'3f} {Н}) ({\u1054\'3f} {О}) ({\u1055\'3f} {П}) ({\u1056\'3f} {Р})
({\u1057\'3f} {С}) ({\u1058\'3f} {Т}) ({\u1059\'3f} {У}) ({\u1060\'3f} {Ф})
({\u1061\'3f} {Х}) ({\u1062\'3f} {Ц}) ({\u1063\'3f} {Ч}) ({\u1064\'3f} {Ш})
({\u1065\'3f} {Щ}) ({\u1066\'3f} {Ъ}) ({\u1067\'3f} {Ы}) ({\u1068\'3f} {Ь})
({\u1069\'3f} {Э}) ({\u1070\'3f} {Ю}) ({\u1071\'3f} {Я})
({\u160\'3f} { }) ({\u8470\'3f} {№})))

; Translit encoding
(set 'translit '(("а" "a") ("б" "b") ("в" "v") ("г" "g") ("д" "d") ("е" "e") ("ё" "yo")
("ж" "zh") ("з" "z") ("и" "i") ("й" "y") ("к" "k") ("л" "l") ("м" "m") ("н" "n")
("о" "o") ("п" "p") ("р" "r") ("с" "s") ("т" "t") ("у" "u") ("ф" "f") ("х" "h")
("ц" "c") ("ч" "ch") ("ш" "sh") ("щ" "sch") ("ъ" "y") ("ы" "y") ("ь" "y") ("э" "e")
("ю" "yu") ("я" "ya") ("А" "A") ("Б" "B") ("В" "V") ("Г" "G") ("Д" "D") ("Е" "E")
("Ё" "YO") ("Ж" "ZH") ("З" "Z") ("И" "I") ("Й" "Y") ("К" "K") ("Л" "L") ("М" "M")
("Н" "N") ("О" "O") ("П" "P") ("Р" "R") ("С" "S") ("Т" "T") ("У" "U") ("Ф" "F")
("Х" "H") ("Ц" "C") ("Ч" "CH") ("Ш" "SH") ("Щ" "SCH") ("Ъ" "Y") ("Ы" "Y") ("Ь" "Y")
("Э" "E") ("Ю" "YU") ("Я" "YA")))

; Internal function. Explodes string to bytes
(define (cortar linea)
(unpack (dup "b" (length linea)) linea))

; Internal function. Applies codetable to the list of bytes
(define (en-de-byte linea tabla (o-linea ""))
(dolist (x (cortar linea))
(push (or (lookup x tabla) (char x)) o-linea -1))
o-linea)

; Internal function. Explodes string to letters
; There can be surprises, becouse both find-all and explode
; should work (in theory) with any encoding.
(define (cyr-explode linea)
(if (= (last (parse ((set-locale) 0) ".")) "1251")
(find-all "." linea)
(explode linea)))

; Usage: (cyr-translit "Щёлочь, кислота, аллюминий") => "SCHyolochy, kislota, allyuminiy"
(define (cyr-translit linea)
(join (map (fn (x) (or (lookup x translit) x)) (cyr-explode linea))))

; Usage: (cyr-translit_ "Василий Пупкин") => "Vasiliy_Pupkin"
(define (cyr-translit_ linea)
(replace " " (cyr-translit linea) "_"))

; Usage: (cyr-rtf "\\u1055\\'3F\\u1086\\'3F\\u1089\\'3F\\u1083\\'3F")
; Decodes russian letters in the .rtf file.
(define (cyr-rtf linea)
(replace {\\u[18][0-9]+\\'3f} linea (lookup (lower-case $it) en-rtf) 1))

; Usage: (cyr-rtf-r "спецификация")
; Encodes a string to the rtf russian letters format (useful for searching
; in raw-rtf). Expect problems with spaces and other \u special symbols.
(define (cyr-rtf-r linea)
(let (letra "" efecto "")
(while (!= (set 'letra (pop linea)) "")
(push (or (lookup letra (map reverse en-rtf)) letra) efecto -1))
(replace "\\" efecto "\\\\")
(replace "'3f" efecto "'3F")))

; Usage: (url-encode-utf "Василий Пупкин")
; Encodes strings for urls (utf)
(define (url-encode-utf t-linea)
(set 't-out "")
(dostring (t-char t-linea)
(push (or (lookup (char t-char) en-url-utf)
(lookup (char t-char) en-url-sym)
(char t-char)) t-out -1))
t-out)

; Usage: (url-encode-win "Василий Пупкин")
; Encodes strings for urls (windows-1251)
(define (url-encode-win t-linea)
(set 't-out "")
(dostring (t-char t-linea)
(push (or (lookup (char t-char) en-url-win)
(lookup (char t-char) en-url-sym)
(char t-char)) t-out -1))
t-out)

; Usage: (cyr-win-utf "text in windows-1251 encoding")
; Decodes text from windows-1251 to utf-8
(define (cyr-win-utf linea)
(en-de-byte linea en-win-1251))

; Usage: (cyr-koi-utf "text in KOI8R encoding")
; Decodes text from KOI8R to utf-8
(define (cyr-koi-utf linea)
(en-de-byte linea en-koi8r))

; Usage: (cyr-utf-win "text in utf-8 encoding")
; Decodes text from utf-8 to win-1251
(define (cyr-utf-win linea)
(let (linea (cortar linea) o-linea "" f-byte nil)
(while (set 'f-byte (pop linea))
(if (or (= f-byte 208) (= f-byte 209))
(push (or (lookup (+ (mul f-byte 256) (or (pop linea) 0)) en-utf-8) "?") o-linea -1)
(if (= f-byte 226)
(push (or
(lookup (+ 14811136 (mul (or (pop linea) 0) 256) (or (pop linea) 0)) en-utf-8)
"?") o-linea -1)
(push (char f-byte) o-linea -1))))
o-linea))

; Functions commented below will be useful for adding another charsets,
; like CP866 or ISO-8859-5 or whatever
;
; Encodes one russian utf char to url-ready form
;(define (url-encode-char t-char)
; (append
; "%" (format "%X" ((unpack "b b" t-char) 0))
; "%" (format "%X" ((unpack "b b" t-char) 1))))
;
;(set 'url-charset '())
;(dolist (x cyr-alpabet)
; (push (append (list x) (list (url-encode-char x))) url-charset -1))
;
; Types list ready to insert to the code
;(set 'counter 2)
;(dolist (x en-win-1251)
; (print "(\"" (x 1) "\" \"%" (format "%X" (x 0)) "\") ")
; (if (= counter 6)
; (begin (print "\n ") (set 'counter 0)))
; (set 'counter (+ counter 1)))

пятница, 26 марта 2010 г.

rand в newLISP + Windows (RAND_MAX)

Где-то в недрах исходников newLISP есть константа RAND_MAX. Для Windows она равна 32767, для Tru64Unix , соответственно, 2147483647. Это, что как, (- (pow 2 31) 1).

Из-за этого в Windows функция rand физически не может сгенерить больше чем 32768 разных случайных чисел. Проверить это довольно просто:

> (length (unique (rand 1000000 1000000)))
32767


Что делать, если нам таки нужно больше случайных чисел?

Лично я сделал функцию (rand_64):

(define (rand_64 top num)
(map (fn (x y) (/ (* (+ (* x 32768) y) top) 1073741824))
(rand 32768 num) (rand 32768 num)))


Если я правильно всё рассчитал (в чём я ни разу не уверен) она умеет генерить уже не 32 тысячи, а примерно миллиард разных случайных чисел.

Сократить список

Задача из олимпиады для детей.

Получив на входе "1,3,4,5,6,7,8,10,12,16,17,20,21,22,23,24" надо отдать на выходе "1,3-8,10,12,16-17,20-24"

Вот моё решение:
(define (pack-2 n n1)
(if (= (- (int n1) (int ((parse (-7 n) "[-,]" 0) -1))) 1)
(append n "-" n1)
(append n "," n1)))

(define (compact in)
(replace "(-([^,]+-)+)" (apply pack-2 (parse in ",") 2) "-" 0))


Всё бы ничего, только скорость не радует. На последовательности в 100 000 символов программа думает аж четыре минуты. А надо миллион, да...

Оптимизировал немного код и свёл его в одну функцию, убрал медленный apply/reduce:

(define (compact in)
(replace "(-([^,]+-)+)"
(replace "([0-9]+),(?=([0-9]+))" in
(if (= (- (int $2) (int $1)) 1) (append $1 "-") $it)
0) "-" 0))


Время обсчёта 100 000 — 0,7 секунды. Время на 1М — 7 секунд. Время на 10М — 66 секунд. Задача выполнена, не?

четверг, 25 марта 2010 г.

Сумма цифр в числе

Прочёл ещё одну историю из IT-happens. Там школьникам поставили задачу подсчитать сумму цифр в числе. Хитропопый школьник заставил пользователя вводить число по одной цифре, а потом сложил все введённые цифры.

Я попробовал повторить подвиг школьника на newLISP. Программа, как и предполагал, уложилась в полстрочки:

(define (sum-digit x) (apply + (map int (explode (string x)))))

bind, sym, save and load

Допустим, мы хотим хранить настройки нашей программы в файле, считывать их оттуда и изменять. Как и что делать:

1. Сохранение настроек

> (set 'params '((var1 1) (var2 2) (var3 3)))
((var1 1) (var2 2) (var3 3))
> (save "params.lsp" 'params)
true


2. Загрузка настроек

> (load "params.lsp")
((var1 1) (var2 2) (var3 3))
> (bind params)
3
> var1
1


Команда bind, грубо говоря, выполняет над элементам списка типа (a b) операцию (set 'a b).

3. Изменение настроек, если имя переменной известно

> (pop-assoc 'var1 params)
(var1 1)
> (push '(var1 5) params)
((var1 5) (var2 2) (var3 3))


4. Изменение настроек, если имя переменной приходит в другой переменной

> (set 'text-var "var1" 'text-value "7")
"7"
> (pop-assoc (sym text-var) params)
(var1 5)
> (push (list (sym text-var) (int text-value)) params)
((var1 7) (var2 2) (var3 3))


Оператор sym преобразует строку "x" в переменную 'x.

среда, 24 марта 2010 г.

smtp (AUTH PLAIN)

Пример короткой беседы через телнет с одним моим знакомым почтовым сервером (пароль и логин пишутся в base64)

telnet smtp.testmail.ru 25
Trying 77.222.41.7...
Connected to smtp.testmail.ru.
Escape character is '^]'.
220 smtp.testmail.ru ESMTP
EHLO robot // Здесь, вероятно, должно быть robot.domain.ru
250-smtp.testmail.ru Hello robot [204.200.222.136]
250-SIZE 26214400
250-PIPELINING
250-AUTH LOGIN PLAIN
250-STARTTLS
250 HELP
AUTH LOGIN
334 VXNlcm5hbWU6 // "Username:"
cm9ib3Q= // robot
334 UGFzc3dvcmQ6 // "Password:"
MTIzNDU= // 12345
235 Authentication succeeded
MAIL FROM: // В пришедшем письме "FROM" было пусто
250 OK
RCPT TO:
250 Accepted
DATA
354 Enter message, ending with "." on a line by itself
Hello, robot!
Привет, робот!
.
250 OK id=1NuY0E-0005EH-QE
quit
221 smtp.testmail.ru closing connection
Connection closed by foreign host.

Ubuntu 10 + Java

В Ubuntu 10 мне почему-то не удалось поставить стандартную Java от Sun. Зато замечательно сработала установка некой "открытой Java":

sudo apt-get install openjdk-6-jdk


Не знаю как другие приложения, а newLISP запустился на ней замечательно.

Клавиша Микрософт плюс шорткаты в Убунту

HowTo: Как запускать в Убунту Writer по нажатию Флаг+W и Calc по нажатию Флаг+X.

1. Система -- Параметры -- Клавиатура -- Раскладки -- Параметры раскладки -- клавиши Alt/Win -- Hyper соответствует клавишам Win
2. Система -- Параметры -- Комбинации клавиш клавиатуры -- управление окнами -- скрыть все окна и активировать рабочий стол -- Miscrosoft+D
3. sudo apt-get install compizconfig-settings-manager
4. ccsm -- General options -- commands -- key bindings -- Run command 0 -- изменить на W.
5. ccsm -- General options -- commands -- key bindings -- Run command 1 -- изменить на X.
6. ccsm -- General options -- commands -- commands -- Run command 0 -- поставить "soffice -writer"
7. ccsm -- General options -- commands -- commands -- Run command 1 -- поставить "soffice -calc"
8. ?????
9. Profit

вторник, 23 марта 2010 г.

Простые числа

Прочёл сейчас в IT happens историю, про мальчика, который решил за 10 минут задачу "написать программу, которая печатает простые числа от 1 до 100". Его программа выглядела так:

10 CLS
20 PRINT "2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97"
30 END


А вот как выглядит моя "десятиминутная" программа, показывающая превосходство newLISP над другими высокоуровневыми языками программирования:

(define (prime? x) (= (first (factor x)) x))
(println (filter prime? (sequence 2 100)))


Забавно, что открыв мануал на странице с описанием функции factor, я наткнулся на готовое решение, написанное, вероятно, самим Лутцем:

(define (primes n , p)
(dotimes (e n)
(if (= (length (factor e)) 1)
(push e p -1))) p)
(print (primes 100))


Решение Лутца, что характерно, работает на 20% быстрее быстрее.

> (time (filter prime? (sequence 2 1000000)))
2203,125
> (time (primes 1000000))
1796,875

Стандартные лог-файлы в Linux

Вот список стандартных лог-файлов в Linux (источник):

/var/log/message: General message and system related stuff
/var/log/auth.log: Authenication logs
/var/log/kern.log: Kernel logs
/var/log/cron.log: Crond logs (cron job)
/var/log/maillog: Mail server logs
/var/log/qmail/ : Qmail log directory (more files inside this directory)
/var/log/httpd/: Apache access and error logs directory
/var/log/lighttpd: Lighttpd access and error logs directory
/var/log/boot.log : System boot log
/var/log/mysqld.log: MySQL database server log file
/var/log/secure: Authentication log
/var/log/utmp or /var/log/wtmp : Login records file
/var/log/yum.log: Yum log files


Надо отметить, что в моей Ubuntu 8.10 присутствуют не все файлы, а кое-какие логи лежат в других местах. Ещё там же предлагаются команды для просмотра лог-файлов:

# tail -f /var/log/messages


Тэйл выводит последние 10 строк файла (можно изменить, набрав tail -20 filename.log). Ключ -f означает "следить за файлом". Если лог меняется, это отображается в реальном времени.

# less /var/log/messages
# more -f /var/log/messages


less и more -- утилиты для просмотра файла с прокруткой. Пробел -- на страницу вниз. Выход -- Q. Ключ -f в more означает "считать только логические строки". Что это значит, я не понял.

newLISP: запись/чтение ini-файлов

; Экранирует строку, помещая в "" и защищая спецсимволы слешами
(define (gnirts-lave x)
(1 -1 (string (list x))))

; Записывает список в .ini-файл
(define (list-ini fl lst)
(write-file fl (join
(map (fn (x) (join (map gnirts-lave x) " ")) lst) "\n")))

; Читает список из .ini-файла
(define (ini-list fl)
(map (fn (x) (map eval-string
(select (regex "([^ ]+) (.+)" x) 3 6)))
(clean null? (parse (or (read-file fl) "") "\n"))))

; Обновить строку в .ini-файле
; Usage: (update-ini "april.ini" "days" "30")
(define (update-ini fl v d)
(let (lst (ini-list fl))
(pop-assoc v lst)
(push (list v d) lst -1)
(list-ini fl lst)))


Пример ini-файла:

"path" "/var/www/main"
"len" "Салют пионерам"
"score" "Special <>\"'\"' symbols"


Примечания:

1. Пустые строки оставлять можно.
2. Комментариями (#) не заморачивался.
3. Если файла нет, вернёт пустой список/создаст файл

newLISP: имена переменных в виде текстовых строк

Нам выдают передают имя переменной в виде текстовой строки. Например:

(set 'mylist '(1 2 3 4 5))
(set 'var "mylist")


Задача. Передать эту переменную в функцию.

Решение 1.

(define (add-list x) (apply + (eval x)))

(add-list (sym var))


Решение 2.

(define (add-list-1 x) (eval-string (append "(apply + " x ")")))

(add-list-1 var)

Base64 на JavaScript

А вот цельнотянутый из webtoolkit.info код для Base64 с моим небольшим инородным вкраплением в виде Base62 кодировки.

/**
*
* Base64 encode / decode
* http://www.webtoolkit.info/
*
**/

//
// Кодирует в формат, удобный для пересылки через GET- и POST- запросы.
// Base62.encode('Смайлик (.Y.)'); => '0KHQvNCw0LnQu9C40LogKC5ZLik_e'
// Base62.decode('0KHQvNCw0LnQu9C40LogKC5ZLik_e'); => 'Смайлик (.Y.)'
//
var Base62 = {

// Кодирует данные в букво-цифро-подчёркивание
encode : function (input) {
return Base64.encode(input).replace(/\//g, "_s").replace(/\+/g, "_p").replace(/=/g, "_e");
},

// Раскодирует данные из букво-цифро-подчёркивания
decode : function (input) {
return Base64.decode(input.replace(/_s/g, "/").replace(/_p/g, "+").replace(/_e/g, "="));
}
}

var Base64 = {

// private property
_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

// public method for encoding
encode : function (input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;

input = Base64._utf8_encode(input);

while (i < input.length) {

chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);

enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;

if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}

output = output +
this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

}

return output;
},

// public method for decoding
decode : function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;

input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

while (i < input.length) {

enc1 = this._keyStr.indexOf(input.charAt(i++));
enc2 = this._keyStr.indexOf(input.charAt(i++));
enc3 = this._keyStr.indexOf(input.charAt(i++));
enc4 = this._keyStr.indexOf(input.charAt(i++));

chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;

output = output + String.fromCharCode(chr1);

if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}

}

output = Base64._utf8_decode(output);

return output;

},

// private method for UTF-8 encoding
_utf8_encode : function (string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";

for (var n = 0; n < string.length; n++) {

var c = string.charCodeAt(n);

if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}

}

return utftext;
},

// private method for UTF-8 decoding
_utf8_decode : function (utftext) {
var string = "";
var i = 0;
var c = c1 = c2 = 0;

while ( i < utftext.length ) {

c = utftext.charCodeAt(i);

if (c < 128) {
string += String.fromCharCode(c);
i++;
}
else if((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i+1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
}
else {
c2 = utftext.charCodeAt(i+1);
c3 = utftext.charCodeAt(i+2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}

}

return string;
}

}

Перенаправление поддомена

Как перенаправить поддомен сайта на другой IP адрес.

1. Заходим на свой хостинг, на страницу управления DNS.
2. Добавляем запись вида forum A 204.200.222.136
3. Теперь адрес forum.mysite.ru резольвится как 204.200.222.136

понедельник, 22 марта 2010 г.

JavaScript онлайн-отладчик

Простейший отладчик кода JavaScript. Нужен, чтобы по быстрому проверить какую-нибудь мелочь в JS или чтобы отладить функцию, которая работает с данными, а не с пользовательским интерфейсом.

<html>
<head>
</head>

<body>
<textarea style="width: 1000; height: 600; border: 1px solid black;" id="script"></textarea>
<br><br>
<button style="width: 1000;" onclick="eval(getElementById('script').value)">Go!</button>
</body>
</html>

Base62 (newLISP)

newLISP вариант:

; Кодирует в буквоцифры и подчёркивание
(define (base62-enc text)
(replace "=" (replace "+" (replace "/" (base64-enc text) "_s") "_p") "_e"))

; Раскодирует из буквоцифр и подчёркивания
(define (base62-dec text)
(base64-dec (replace "_e" (replace "_p" (replace "_s" text "/") "+") "=")))


JavaScript вариант:

var Base62 = {

// Кодирует данные в букво-цифро-подчёркивание
encode : function (input) {
return Base64.encode(input).replace(/\//g, "_s").replace(/\+/g, "_p").replace(/=/g, "_e");
},

// Раскодирует данные из букво-цифро-подчёркивания
decode : function (input) {
return Base64.decode(input.replace(/_s/g, "/").replace(/_p/g, "+").replace(/_e/g, "="));
}
}

Javascript: передать данные в скрипт

Как передать данные в файл, не прогоняя пользователя со страницы?

Пункт первый. Делаем невидимый iframe:

<iframe style='display: none' id='fieldPsFrm' src='save.cgi' width='0' height='0' align='left'></iframe>

Пункт второй. Изменяем его location на нужный:

document.getElementById('fieldPsFrm').contentWindow.location = 'save.cgi?test=test1&test2=test3';

Комментарии:

1. Вообще, наверное, надо бы делать эту хрень через AJAX.
2. Надо будет запилить отдельную функцию, чтобы передавала параметры сама.

(time) и newLISP

Функция time, оказывается, считает в Linux чуть ли не микросекунды, а вот в Windows минимальный квант времени -- 1/64 секунды (15.625 ms).

Ещё в time есть вызванный переполнением int32 баг: 0x7FFFFFFF микросекунд -- это примерно 35 минут. Поэтому интервалы длиннее 35 минут считаются неправильно.

суббота, 20 марта 2010 г.

Windows: много файлов в одной папке

Решил из любопытства повторить вот этот опыт с максимальным числом открытых файлов в Windows.

Создал скрипт, начал генерить файлы:

(module "crypto.lsp")
(make-dir "c:/tmp/testdir")
(change-dir "c:/tmp/testdir")

(define (rand-2k)
(join (map (fn (x) (append (string x) ": " (join
(map string (rand 10 50))))) (sequence 0 37)) "\n"))

(time (dotimes (x 10000)
(write-file (append (crypto:md5 (string (random))) ".txt") (rand-2k))))
(time (dotimes (x 90000)
(write-file (append (crypto:md5 (string (random))) ".txt") (rand-2k))))


Первые десять тысяч файлов генерировались 123 секунды. Следующие 90 тысяч файлов -- уже 1615 секунд.

Скорость работы, в целом, меня устроила.

Получить список всех файлов в папке через (directory) —- 0.1 секунды
Отобрать 200 файлов и прочесть их -- те же 0.1 секунды.
Сделать то же самое командой "dir" -- примерно столько же.
Прочесть файл из папки -- 0 миллисекунд (!).

И всё бы ничего, да вот только, блджад, dir показал, что у меня в папке 31 169 файлов! Я вначале даже подумал было, что это предел. Однако краткое расследование показало, что это не предел, во всём виноват генератор случайных чисел.

Вот здесь:
http://www.oszone.ru/1296/
http://forum.searchengines.ru/showthread.php?t=290056

пишут, что число файлов в некорневой папке неограничено и может достигать 25 миллионов...

Для продолжения эксперимента оставлю как-нибудь компьютер на ночь создавать файлы безо всяких md5, просто по номерам (sequence 1 500000). Если предположить, что скорость не будет меняться значительно, это должно занять несколько часов. Посмотрим, как поведёт себя Windows в равных условиях, а не на детских 32k файлов.

Кстати, del. удалил эти файлы за 20 секунд.

--- Продолжение ---

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

На ста тысячах скорость была более-менее, но уже не фонтан.

--- Продолжение ---

Сделал скриптом из-под Ubuntu миллион файлов. Скрипт работал примерно 40 минут, жёсткий диск при этом практически не было слышно. Вроде бы -- та же папка, та же файловая система... Интересно, почему так? Возможно, Ubuntu просто пишет на диск не после каждого файла, а раз в несколько секунд?

После перезагрузки обратно в Windows начал тестировать производительность. Она, сразу скажу, меня не очень порадовала (время newLISP выдаёт в миллисекундах):

> (time (directory "."))
13906,25
> (time (directory "."))
2343,75
> (time (directory "." "7.*"))
2187,5
> (length (directory "." "7.*"))
468559
> (length (directory "." "777.*"))
1999
> (time (map read-file (directory "." "777.*")))
36406,25
> (time (read-file "123_456.txt"))
31,25
> (time (read-file "123_456.txt"))
0
> (time (read-file "234_567.txt"))
15,625
> (time (read-file "345_678.txt"))
15,625


13 секунд чтобы получить список файлов в папке первый раз, 2 секунды на каждое последующее обращение к списку файлов. В целом, терпимо.

36 секунд на прочесть 2 000 файлов -- тоже, в общем, сойдёт. Получается всего лишь 18 миллисекунд на файл.

Забавно, что повторное чтение файла занимает 0 миллисекунд. Правда, я не знаю, как именно считает скорость newLISP. Подозреваю, что значениям меньше миллисекунды верить не стоит.

Linux: максимальное количество файлов в папке

Цель эксперимента: определить, насколько комфортно будет ворочаться мой Linux в папке с большим количеством файлов.

Компьютер: Intel(R) Core(TM)2 Duo CPU E7300 @ 2.66GHz
Жёсткий диск: Seagate Barracuda ES SATA 3.0Gb/s 500Gb
Файловая система: wubi/ntfs/ext3 (использует раздел Widnows)
Операционная система: Ubuntu 8.10

Фаза 1. Создание файлов

Так как свободных инодов было всего 640 тысяч, я принял решение сгенерить полмиллиона файлов. Размер файлов был выбран в 2 килобайта.

Вот скрипт (newLISP):

(module "crypto.lsp")
(make-dir "/home/hilo/testdir")
(change-dir "/home/hilo/testdir")

(define (rand-2k)
(join (map (fn (x) (append (string x) ": " (join
(map string (rand 10 50))))) (sequence 0 37)) "\n"))

(time (dotimes (x 500000)
(write-file (append (crypto:md5 (string (random))) ".txt") (rand-2k))))


Время генерации файлов: 17 минут.

Фаза 2. Тест производительности

; Найти файлы по маске
(directory "." "7.*") ; 0,5с.

; Получить список вообще всех файлов
(directory ".") ; те же 0,5с.

; Отобрать 200 файлов и прочесть их из папки в память:
(map (fn (x) (read-file x)) (directory "." "6666.*")) ; Две секунды

; Прочесть один файл с известным именем:
(read-file "7777cc087494c9e6e474ac9fe6c01dfc.txt") ; 0,013 ms

; Прочесть файл из папки, в которой лежит всего несколько файлов:
(time (read-file "../ffec6ba54ea01fd951dc2433334abd0c.txt")) ; 0,014 ms

Вывод 1: скорость чтения файлов никак не зависит от того, сколько у них соседей в папке.
Вывод 2: файлы читаются мгновенно.
Вывод 3: непонятно, почему чтение 200 предварительно отобранных файлов занимает две секунды -- должно занимать полсекунды. Надо полагать, если сделать отбор имён, подождать две секунды, а потом прочесть их -- чтение займёт не полторы секунды, а несколько миллисекунд.

Фаза 3. Работа со встроенными командами Linux

3.1. Поиск через grep
grep -l "777" 5555* # 3 cекунды

3.2. ls
ls testdir > test.txt # 3 секунды (создался файл на 18 Мb)

3.3. Подсказка имени. Нажатие "Tab" для подсказки подвесило систему примерно на одну минуту, потом всё отвисло.

3.4. rm
rm * вылетел через 40 секунд с ошибкой:
"bash: /bin/rm: Слишком длинный список аргументов."

Выводы. Встроенные средства справляются с 500 тысячами файлов херовато. Самое разумное: сделать сначала ls в файл, а потом работать уже с этим файлом.

Фаза 4. Удаление файлов

(map delete-file (directory ".")) ; две минуты, под бодрое шуршание диска.

На начало эксперимента было свободно 641373 инодов из 856800. После окончания эксперимента свободных инодов стало 641279. Куда зажевались примерно 100 инодов -- неясно. Вероятно, на временные файлы операционной системы и временные файлы newLISP.

Общий вывод. Для серьёзной нагрузки 500 000 файлов -- это довольно натужно. Я бы предпочёл иметь запас по скорости раз в десять от демонстрируемого.

Впрочем, сначала я собираюсь обновить систему до 9.10 и посмотреть, насколько бодро пойдёт работа со встроенной файловой системой.

--- Продолжение ---

Проапгрейдился до 9.10. Файловая система ext4, созданная непосредственно на диске, без всяких промежуточных ntfs. Также немного упростил скрипт:

(make-dir "/tmp/testdir")
(change-dir "/tmp/testdir")

(define (string-append) (apply append (map string (args))))

(define (rand-2k x)
(write-file (string-append x ".txt")
(join (map (fn (x) (append (string x) ": " (join
(map string (rand 10 50))))) (sequence 0 37)) "\n")))

(define (gen-1k y)
(dotimes (x 1000) (rand-2k (string-append y "_" x)))
(println y))

(time (map gen-1k (sequence 0 999)))


Миллион файлов на этот раз создавался 32 минуты. В общем, практически столько же, сколько и в прошлый раз. А вот тайминг (время newLISP сообщает в миллисекундах):

> (time (directory "."))
563,001
> (time (directory "." "666.*"))
572,296 ; Это полсекунды
> (length (directory "."))
1000002
> (time (map read-file (directory "." "666.*")))
9913,617 ; 5 миллисекунд на файл
> (time (map read-file (directory "." "668_.*")))
1186,284 ; 1 миллисекунда на файл


Как видно, скорость поиска в директории увеличилась по сравнению с ntfs в два раза, а вот скорость чтения файлов почему-то сильно скачет. В дальнейших экспериментах скорость чтения одного файла составляла 10-50 миллисекунд на первый заход и примерно 0,02 миллисекунды на чтение кэшированного файла. Файлы, напомню, имеют размер в два килобайта.

Итоговый вывод остался неизменным: работать с миллионом файлов в одной папке можно, если имена файлов известны. Если требуется поиск -- возможны варианты. Полсекунды на поиск иногда уже чересчур.

Удаление заняло примерно полчаса:
(map delete-file (directory "."))


Затем тем же скриптом я успешно нагенерил минут за 40 миллион файлов для Windows:
http://hilocomod.blogspot.com/2010/03/windows.html

пятница, 19 марта 2010 г.

Javascript rollover

А вот тот самый маленький ролловер, ради которого и затевался робот на Script-Fu

<!-- Кнопки -->
<style type="text/css">
/* Общие для всех кнопок */
.bcm a { display: block; height: 52px; float: left; position: absolute; top: 0px; }
.bcm a:hover { background-position: 0px -52px; }
a:focus { outline:0; }

/* По каждой кнопочке отдельно */
.b_about a { background-image: url('about.png'); width: 97px; left: 0px; }
.b_reg a { background-image: url('reg.png'); width: 190px; left: 103px; }
</style>

<div style="width: 1024; margin-bottom: 10; position: relative;">
<div class='bcm b_about'><a href='http://test.none' target="_blank"> </a></div>
<div class='bcm b_reg'><a href="javascript:Test('test');"> </a></div>
</div>


Примечания.

1. Вставлять ширину каждой кнопки в style не получилось: важно, что это именно ссылка и после .b_reg идёт a.

2. a:focus { outline:0; } нужен, чтобы после нажатия на див не оставалось мерзкой красной рамочки.

3. Сделав кнопки первый раз я обнаружил, что высота в 54 выбрана мной опрометчиво: надо 52. Тут-то я и порадовался, что смастерил себе робота, а не начал ресайзить/клеить дедовским способом, руками.

Узнать размеры PNG-файла

Функция на newLISP.

; Возвращает размеры png-файла (ширину и высоту)
(define (get-png-size file-name)
(map eval-string (map (curry append "0x")
(select (regex {49484452....(....)....(....)}
(join (map (curry format "%02X")
(unpack (dup "b" 100) (read-file file-name))))) 3 6))))

Script-Fu: resize, combine, vectors, regexp

Текущая библиотека разных моих мелких функций на Скрипт-Фу. Очень жаль, что Блогспот не даёт прикладывать файлы в виде файлов целиком.

;
; Расширения Script-Fu
;

; Возвращает список файлов из dir
; Пример: (hl-file-list "/tmp")
(define (hl-file-list dir)
(cadr (file-glob (string-append dir "/*") 0)))

; Загрузить файл jpg
; Пример: (hl-jpeg-load "/tmp/butt/video1.jpg")
; Возвращает: image
(define (hl-jpeg-load filename)
(car (file-jpeg-load RUN-NONINTERACTIVE filename filename)))

; Склеить вертикально два одинаковых по размеру файла jpg в один
(define (hl-jpeg-vcombine file1 file2 file3)
(let* (
(img1 (hl-jpeg-load file1))
(img2 (hl-jpeg-load file2))
(h-height (car (gimp-image-height img1)))
(f-width (car (gimp-image-width img1)))
; Создадим "подложку"
(img (car (gimp-image-new f-width (* h-height 2) RGB)))
; Обозначим локальные переменные
(dra) (dra-copy))
; Снимем копию дравабле первого файла и добавим её
; в верхнюю часть подложки
(set! dra-copy (car (gimp-image-get-active-layer img1)))
(set! dra-copy (car (gimp-layer-new-from-drawable dra-copy img)))
(gimp-image-add-layer img dra-copy -1)
; Добавим копию второго имаге вниз подложки
(set! dra-copy (car (gimp-image-get-active-layer img2)))
(set! dra-copy (car (gimp-layer-new-from-drawable dra-copy img)))
(gimp-image-add-layer img dra-copy -1)
(gimp-layer-set-offsets dra-copy 0 h-height)
; Сохраним изображение в file3
(set! dra (car (gimp-image-merge-visible-layers img 0)))
(file-png-save 1 img dra file3 file3 0 9 0 0 0 1 1)
; Подчистим за собой мусор
(hl-delete-images (list img img1 img2))))

; Удаляет загруженные изображения из памяти
(define (hl-delete-images image-list)
(map (lambda (x) (gimp-image-delete x)) image-list))

; Уменьшить размеры jpg. new-len: новая длина стороны
; flag: "w" или "h", смотря по какой стороне ориентироваться с размерами
(define (hl-resize-jpg file-in file-out new-len flag)
(let* (
(img (car (file-jpeg-load 1 file-in file-in)))
(dra (car (gimp-image-get-active-layer img)))
(old-h (car (gimp-image-height img)))
(old-w (car (gimp-image-width img)))
(new-h) (new-w))
; Вычислим новые размеры картинки
(if (string=? flag "w")
(begin
(set! new-w new-len)
(set! new-h (* old-h (/ new-w old-w))))
(begin
(set! new-h new-len)
(set! new-w (* old-w (/ new-h old-h)))))
; Изменим размеры jpg
(gimp-drawable-transform-scale dra 0 0 new-w new-h 0 3 FALSE 3 2)
(gimp-image-crop img new-w new-h 0 0)
; Сохраним картинку
(file-jpeg-save 1 img dra file-out file-out .95 0 0 0 " " 0 1 0 1)
; Очистим мусор
(gimp-image-delete img)))

; Создать картинку размерами x y
(define (hl-make-image x y)
(let* (
(img (car (gimp-image-new x y RGB)))
(dra (car (gimp-layer-new img x y RGB-IMAGE "Background" 100 NORMAL-MODE))))
(gimp-image-add-layer img dra -1)
(gimp-edit-clear dra)
(list img dra)))

; Выбрать область с закруглёнными краями
(define (hl-select-round img x y ox oy)
(gimp-rect-select img x y ox oy REPLACE FALSE 0)
(script-fu-selection-rounded-rectangle img FALSE 50 FALSE))

;
; Расширения TinyScheme
;

; Отрезает последние num символов от строки
(define (chop str num)
(substring str 0 (- (string-length str) num)))

; Проверяет, принадлежит ли значение списку
; Пример: (in-list? 3 '(1 2 3 4 5 6))
(define (in-list? itm lst)
(let* (
(found 0))
(for-each (lambda (x)
(if ((if (string? x) string=? =) x itm) (set! found (+ found 1))))
lst)
(if (> found 0) #t #f)))

; Возвращает уникальные значения списка
(define (unique lst)
(let* (
(out-list '()))
(for-each (lambda (x)
(if (in-list? x out-list) #f (set! out-list (append out-list (list x)))))
lst)
out-list))

; Вырезать имя файла из полного пути
; (hl-filename "/tmp/test.jpg") -> "test.jpg"
(define (hl-filename path)
(let* (
(f-len (string-length path))
(s-len 1))
(while (and
(<= s-len f-len)
(not (re-match "/" (substring path (- f-len s-len)))))
(set! s-len (+ s-len 1)))
(substring path (+ (- f-len s-len) 1))))

;
; Рабочие процедуры
;

; Загрузить кучу кнопок вида "test1.jpg" и "test2.jpg", изменить их
; размер до 54 по вертикали, склеить их попарно в файлы для rollover
; кнопок на сайте.
(define (hl-resize-buttons)
(let* (
(in-dir "/home/oleg/gimp/indir")
(out-dir "/home/oleg/gimp/outdir")
; В button-list лежит список файлов типа '("test", "about", "home")
(button-list (unique (map hl-filename (map
(lambda (x) (chop x 5)) (hl-file-list in-dir))))))
(for-each (lambda (x)
; Делаем ресайз кнопок
(hl-resize-jpg (string-append in-dir "/" x "1.jpg")
(string-append out-dir "/" x "1.jpg") 54 "h")
(hl-resize-jpg (string-append in-dir "/" x "2.jpg")
(string-append out-dir "/" x "2.jpg") 54 "h")
(hl-jpeg-vcombine
(string-append out-dir "/" x "1.jpg")
(string-append out-dir "/" x "2.jpg")
(string-append out-dir "/" x ".png")))
button-list)))

; Сделать кнопку в стиле веб-два-ноль с заданным текстом
; (hl-button "Нажмите здесь!" "Courier" "sample.png" '(224 224 255) '(80 80 255) '(255 255 255))
; Цвета (1-2-3): фон, цвет кнопки, цвет текста
(define (hl-button text font name color-1 color-2 color-3)
(let* (
(tx (car (gimp-text-get-extents-fontname text 26 0 font)))
(ty (cadr (gimp-text-get-extents-fontname text 26 0 font)))
(image (nl-make-image (+ tx 44) 44)))

; Очищаем фон, ставим бэкграунд
(gimp-context-set-foreground color-1)
(gimp-edit-fill (cadr image) 0)
(gimp-context-set-background color-1)

; Заполняем большой прямоугольник
(hl-select-round (car image) 4 4 (+ tx 30) ty)
(gimp-context-set-foreground color-2)
(gimp-edit-fill (cadr image) 0)

; Отбрасываем тень
(script-fu-drop-shadow (car image) (cadr image) 4 4 6 color-2 80 0)

; Заполняем малый прямоугольник градиентом
(hl-select-round (car image) 6 6 (+ tx 26) (- ty 12))
(gimp-blend (cadr image) FG-BG-RGB-MODE NORMAL-MODE GRADIENT-LINEAR
100 0 REPEAT-NONE FALSE FALSE 0 0 FALSE 0 (- ty 12) 0 -26)

; Добавляем текст
(gimp-context-set-foreground color-3)
(gimp-text-fontname (car image) (cadr image) 19 4 text 0 TRUE 26 1 font)

; Сохраняем файл
(gimp-image-merge-visible-layers (car image) 0)
(file-png-save 1
(car image)
(car (gimp-image-get-active-layer (car image)))
name
name
0 9 0 0 0 1 1)

; Удаляем изображение из памяти
(gimp-image-delete (car image))))

; Нарисовать карандаш 24х24 пиксела
; Работаем с векторами и кривыми Безье. Сделав всё в
; векторном виде, закрашиваем и изменяем размер с 240х240 до 24х24
; Получается а-ля-иконка
(define (hl-pencil)
(let* (
(img (hl-make-image 240 240))
(image (car img))
(layer (cadr img))
(pbody 0)
(stro 0)
(nose 0))

; Очищаем фон
(gimp-context-set-foreground '(244 244 255))
(gimp-edit-fill layer 0)

; Задаём цвет карандаша
(gimp-context-set-foreground '(0 0 255))

; Новые вектора
(set! pbody (car (gimp-vectors-new image "pencil")))
(gimp-image-add-vectors image pbody -1)

; Рисуем туловище карандаша
(set! stro (car (gimp-vectors-bezier-stroke-new-moveto pbody 180 20)))
(gimp-vectors-bezier-stroke-lineto pbody stro 70 130)
(gimp-vectors-bezier-stroke-conicto pbody stro 105 135 110 170)
(gimp-vectors-bezier-stroke-lineto pbody stro 220 60)
(gimp-vectors-bezier-stroke-conicto pbody stro 215 25 180 20)

; Закрашиваем туловище
(gimp-vectors-to-selection pbody 0 TRUE FALSE 0 0)
(gimp-blend layer FG-BG-RGB-MODE NORMAL-MODE GRADIENT-LINEAR
100 0 REPEAT-NONE FALSE FALSE 0 0 FALSE 125 75 240 240)
(gimp-selection-none image)

; Рисуем носик карандаша
(set! nose (car (gimp-vectors-new image "pencil")))
(gimp-image-add-vectors image nose -1)
(set! stro (car (gimp-vectors-bezier-stroke-new-moveto nose 70 130)))
(gimp-vectors-bezier-stroke-lineto nose stro 20 220)
(gimp-vectors-bezier-stroke-lineto nose stro 110 170)
(gimp-vectors-bezier-stroke-conicto nose stro 105 135 70 130)

; Закрашиваем носик
(gimp-vectors-to-selection nose 0 TRUE FALSE 0 0)
(gimp-blend layer FG-BG-RGB-MODE NORMAL-MODE GRADIENT-LINEAR
100 0 REPEAT-NONE FALSE FALSE 0 0 FALSE -30 30 170 230)
(gimp-selection-none image)

; Изменяем размер
(gimp-image-scale image 24 24)

(gimp-display-new image)))

;
; Отладочные участки
;

; Показываем изображение
; (gimp-display-new img)

; Список шрифтов (можно напамить на него hl-button)
; (gimp-fonts-get-list "")

; Отштриховываем вектора
; (gimp-context-set-brush "Circle (03)")
; (gimp-context-set-foreground '(0 0 255))
; (gimp-edit-stroke-vectors layer nose)

Grep: найти в файлах

Найти строку в нескольких файлах:
grep -lir "TODO" request*

-l: выводить только имена файлов с совпадениями
-r: искать рекурсивно
-i: игнорировать case

Найти строку в одном файле:
grep "video1.jpg" filelist.txt

четверг, 18 марта 2010 г.

Команды/константы Script-Fu

Более-менее полный список команд найти несложно, если погуглить что-нибудь типа "file-bmp-load" "file-bz2-load".

Ещё несколько забавных команд лежит в файле .gimprc (не пользовательском, а в основном, в /etc).

Польза от все этого непотребства, впрочем, сомнительна.

Единственное, что я пока нашёл ценного, две константы:

RUN-INTERACTIVE (0)
RUN-NONINTERACTIVE (1)

Кое-что любопытное (скрипт массовой обработки фотографий):
http://svvord.livejournal.com/882.html

Надо бы, наверное, сказать тому товарищу, что вместо (define) лучше назначать переменные через let*...

dir или ls в Script-Fu

Сбылась мечта бесчисленных поколений. Я разобрался как делать "ls" в Script-Fu.

> (file-glob "c:\\tmp\\Тест\\*" 0)
(7 ("c:\\tmp\\Тест\\test.bmp" "c:\\tmp\\Тест\\test.dds" "c:\\tmp\\Тест\\test.png" "c:\\tmp\\Тест\\test1.bmp" "c:\\tmp\\Тест\\test1.gif" "c:\\tmp\\Тест\\test2.bmp" "c:\\tmp\\Тест\\tmp.sql"))

> (nth 2 (cadr (file-glob "c:\\tmp\\Тест\\*" 0)))
"c:\\tmp\\Тест\\test.png"

> (file-glob "c:\\tmp\\Тест\\*.bmp" 0)
(3 ("c:\\tmp\\Тест\\test.bmp" "c:\\tmp\\Тест\\test1.bmp" "c:\\tmp\\Тест\\test2.bmp"))


Второй параметр у file-glob -- кодировка возвращаемых имён. В Windows, вроде как, ни на что не влияет.

Отступы в коде внутри html

Вот здесь лежит любопытный обзор "indenting with html":
http://www.blooberry.com/indexdot/html/topics/indent.htm

Похоже, проще всего форматировать текст через тэг <pre>. Внутри тэга все пробелы и переводы строк сохраняются, а вот "<>" нужно заэкранивать через "&lt;" и "&gt;".

Кстати, "lt" расшифровывается как "less than", a gt как "greater than".

(Ещё, конечно, всегда можно настроить скрипт, который будет делать для нас форматирование через CSS).

TinyScheme Manual

Взят из исходников GIMP 2.67

                       TinySCHEME Version 1.38

"Safe if used as prescribed"
-- Philip K. Dick, "Ubik"

This software is open source, covered by a BSD-style license.
Please read accompanying file COPYING.
-------------------------------------------------------------------------------

This Scheme interpreter is based on MiniSCHEME version 0.85k4
(see miniscm.tar.gz in the Scheme Repository)
Original credits in file MiniSCHEMETribute.txt.

D. Souflis (dsouflis@acm.org)

-------------------------------------------------------------------------------
What is TinyScheme?
-------------------

TinyScheme is a lightweight Scheme interpreter that implements as large
a subset of R5RS as was possible without getting very large and
complicated. It is meant to be used as an embedded scripting interpreter
for other programs. As such, it does not offer IDEs or extensive toolkits
although it does sport a small top-level loop, included conditionally.
A lot of functionality in TinyScheme is included conditionally, to allow
developers freedom in balancing features and footprint.

As an embedded interpreter, it allows multiple interpreter states to
coexist in the same program, without any interference between them.
Programmatically, foreign functions in C can be added and values
can be defined in the Scheme environment. Being a quite small program,
it is easy to comprehend, get to grips with, and use.

Known bugs
----------

TinyScheme is known to misbehave when memory is exhausted.


Things that keep missing, or that need fixing
---------------------------------------------

There are no hygienic macros. No rational or
complex numbers. No unwind-protect and call-with-values.

Maybe (a subset of) SLIB will work with TinySCHEME...

Decent debugging facilities are missing. Only tracing is supported
natively.


Scheme Reference
----------------

If something seems to be missing, please refer to the code and
"init.scm", since some are library functions. Refer to the MiniSCHEME
readme as a last resort.

Environments
(interaction-environment)
See R5RS. In TinySCHEME, immutable list of association lists.

(current-environment)
The environment in effect at the time of the call. An example of its
use and its utility can be found in the sample code that implements
packages in "init.scm":

(macro (package form)
`(apply (lambda ()
,@(cdr form)
(current-environment))))

The environment containing the (local) definitions inside the closure
is returned as an immutable value.

(defined? <symbol>) (defined? <symbol> <environment>)
Checks whether the given symbol is defined in the current (or given)
environment.

Symbols
(gensym)
Returns a new interned symbol each time. Will probably move to the
library when string->symbol is implemented.

Directives
(gc)
Performs garbage collection immediatelly.

(gcverbose) (gcverbose <bool>)
The argument (defaulting to #t) controls whether GC produces
visible outcome.

(quit) (quit <num>)
Stops the interpreter and sets the 'retcode' internal field (defaults
to 0). When standalone, 'retcode' is returned as exit code to the OS.

(tracing <num>)
1, turns on tracing. 0 turns it off. (Only when USE_TRACING is 1).

Mathematical functions
Since rationals and complexes are absent, the respective functions
are also missing.
Supported: exp, log, sin, cos, tan, asin, acos, atan, floor, ceiling,
trunc, round and also sqrt and expt when USE_MATH=1.
Number-theoretical quotient, remainder and modulo, gcd, lcm.
Library: exact?, inexact?, odd?, even?, zero?, positive?, negative?,
exact->inexact. inexact->exact is a core function.

Type predicates
boolean?,eof-object?,symbol?,number?,string?,integer?,real?,list?,null?,
char?,port?,input-port?,output-port?,procedure?,pair?,environment?',
vector?. Also closure?, macro?.

Types
Types supported:

Numbers (integers and reals)
Symbols
Pairs
Strings
Characters
Ports
Eof object
Environments
Vectors

Literals
String literals can contain escaped quotes \" as usual, but also
\n, \r, \t, \xDD (hex representations) and \DDD (octal representations).
Note also that it is possible to include literal newlines in string
literals, e.g.

(define s "String with newline here
and here
that can function like a HERE-string")

Character literals contain #\space and #\newline and are supplemented
with #\return and #\tab, with obvious meanings. Hex character
representations are allowed (e.g. #\x20 is #\space).
When USE_ASCII_NAMES is defined, various control characters can be
refered to by their ASCII name.
0 #\nul 17 #\dc1
1 #\soh 18 #\dc2
2 #\stx 19 #\dc3
3 #\etx 20 #\dc4
4 #\eot 21 #\nak
5 #\enq 22 #\syn
6 #\ack 23 #\etv
7 #\bel 24 #\can
8 #\bs 25 #\em
9 #\ht 26 #\sub
10 #\lf 27 #\esc
11 #\vt 28 #\fs
12 #\ff 29 #\gs
13 #\cr 30 #\rs
14 #\so 31 #\us
15 #\si
16 #\dle 127 #\del

Numeric literals support #x #o #b and #d. Flonums are currently read only
in decimal notation. Full grammar will be supported soon.

Quote, quasiquote etc.
As usual.

Immutable values
Immutable pairs cannot be modified by set-car! and set-cdr!.
Immutable strings cannot be modified via string-set!

I/O
As per R5RS, plus String Ports (see below).
current-input-port, current-output-port,
close-input-port, close-output-port, input-port?, output-port?,
open-input-file, open-output-file.
read, write, display, newline, write-char, read-char, peek-char.
char-ready? returns #t only for string ports, because there is no
portable way in stdio to determine if a character is available.
Also open-input-output-file, set-input-port, set-output-port (not R5RS)
Library: call-with-input-file, call-with-output-file,
with-input-from-file, with-output-from-file and
with-input-output-from-to-files, close-port and input-output-port?
(not R5RS).
String Ports: open-input-string, open-output-string, get-output-string,
open-input-output-string. Strings can be used with I/O routines.

Vectors
make-vector, vector, vector-length, vector-ref, vector-set!, list->vector,
vector-fill!, vector->list, vector-equal? (auxiliary function, not R5RS)

Strings
string, make-string, list->string, string-length, string-ref, string-set!,
substring, string->list, string-fill!, string-append, string-copy.
string=?, string<?, string>?, string>?, string<=?, string>=?.
(No string-ci*? yet). string->number, number->string. Also atom->string,
string->atom (not R5RS).

Symbols
symbol->string, string->symbol

Characters
integer->char, char->integer.
char=?, char<?, char>?, char<=?, char>=?.
(No char-ci*?)

Pairs & Lists
cons, car, cdr, list, length, map, for-each, foldr, list-tail,
list-ref, last-pair, reverse, append.
Also member, memq, memv, based on generic-member, assoc, assq, assv
based on generic-assoc.

Streams
head, tail, cons-stream

Control features
Apart from procedure?, also macro? and closure?
map, for-each, force, delay, call-with-current-continuation (or call/cc),
eval, apply. 'Forcing' a value that is not a promise produces the value.
There is no call-with-values, values, nor dynamic-wind. Dynamic-wind in
the presence of continuations would require support from the abstract
machine itself.

Property lists
TinyScheme inherited from MiniScheme property lists for symbols.
put, get.

Dynamically-loaded extensions
(load-extension <filename without extension>)
Loads a DLL declaring foreign procedures.

Esoteric procedures
(oblist)
Returns the oblist, an immutable list of all the symbols.

(macro-expand <form>)
Returns the expanded form of the macro call denoted by the argument

(define-with-return (<procname> <args>...) <body>)
Like plain 'define', but makes the continuation available as 'return'
inside the procedure. Handy for imperative programs.

(new-segment <num>)
Allocates more memory segments.

defined?
See "Environments"

(get-closure-code <closure>)
Gets the code as scheme data.

(make-closure <code> <environment>)
Makes a new closure in the given environment.

Obsolete procedures
(print-width <object>)

Programmer's Reference
----------------------

The interpreter state is initialized with "scheme_init".
Custom memory allocation routines can be installed with an alternate
initialization function: "scheme_init_custom_alloc".
Files can be loaded with "scheme_load_file". Strings containing Scheme
code can be loaded with "scheme_load_string". It is a good idea to
"scheme_load" init.scm before anything else.

External data for keeping external state (of use to foreign functions)
can be installed with "scheme_set_external_data".
Foreign functions are installed with "assign_foreign". Additional
definitions can be added to the interpreter state, with "scheme_define"
(this is the way HTTP header data and HTML form data are passed to the
Scheme script in the Altera SQL Server). If you wish to define the
foreign function in a specific environment (to enhance modularity),
use "assign_foreign_env".

The procedure "scheme_apply0" has been added with persistent scripts in
mind. Persistent scripts are loaded once, and every time they are needed
to produce HTTP output, appropriate data are passed through global
definitions and function "main" is called to do the job. One could
add easily "scheme_apply1" etc.

The interpreter state should be deinitialized with "scheme_deinit".

DLLs containing foreign functions should define a function named
init_<base-name>. E.g. foo.dll should define init_foo, and bar.so
should define init_bar. This function should assign_foreign any foreign
function contained in the DLL.

The first dynamically loaded extension available for TinyScheme is
a regular expression library. Although it's by no means an
established standard, this library is supposed to be installed in
a directory mirroring its name under the TinyScheme location.


Foreign Functions
-----------------

The user can add foreign functions in C. For example, a function
that squares its argument:

pointer square(scheme *sc, pointer args) {
if(args!=sc->NIL) {
if(sc->isnumber(sc->pair_car(args))) {
double v=sc->rvalue(sc->pair_car(args));
return sc->mk_real(sc,v*v);
}
}
return sc->NIL;
}

Foreign functions are now defined as closures:

sc->interface->scheme_define(
sc,
sc->global_env,
sc->interface->mk_symbol(sc,"square"),
sc->interface->mk_foreign_func(sc, square));


Foreign functions can use the external data in the "scheme" struct
to implement any kind of external state.

External data are set with the following function:
void scheme_set_external_data(scheme *sc, void *p);

As of v.1.17, the canonical way for a foreign function in a DLL to
manipulate Scheme data is using the function pointers in sc->interface.

Standalone
----------

Usage: tinyscheme -?
or: tinyscheme [<file1> <file2> ...]
followed by
-1 <file> [<arg1> <arg2> ...]
-c <Scheme commands> [<arg1> <arg2> ...]
assuming that the executable is named tinyscheme.

Use - in the place of a filename to denote stdin.
The -1 flag is meant for #! usage in shell scripts. If you specify
#! /somewhere/tinyscheme -1
then tinyscheme will be called to process the file. For example, the
following script echoes the Scheme list of its arguments.

#! /somewhere/tinyscheme -1
(display *args*)

The -c flag permits execution of arbitrary Scheme code.


Error Handling
--------------

Errors are recovered from without damage. The user can install his
own handler for system errors, by defining *error-hook*. Defining
to '() gives the default behavior, which is equivalent to "error".
USE_ERROR_HOOK must be defined.

A simple exception handling mechanism can be found in "init.scm".
A new syntactic form is introduced:

(catch <expr returned exceptionally>
<expr1> <expr2> ... <exprN>)

"Catch" establishes a scope spanning multiple call-frames
until another "catch" is encountered.

Exceptions are thrown with:

(throw "message")

If used outside a (catch ...), reverts to (error "message").

Example of use:

(define (foo x) (write x) (newline) (/ x 0))

(catch (begin (display "Error!\n") 0)
(write "Before foo ... ")
(foo 5)
(write "After foo"))

The exception mechanism can be used even by system errors, by

(define *error-hook* throw)

which makes use of the error hook described above.

If necessary, the user can devise his own exception mechanism with
tagged exceptions etc.


Reader extensions
-----------------

When encountering an unknown character after '#', the user-specified
procedure *sharp-hook* (if any), is called to read the expression.
This can be used to extend the reader to handle user-defined constants
or whatever. It should be a procedure without arguments, reading from
the current input port (which will be the load-port).


Colon Qualifiers - Packages
---------------------------

When USE_COLON_HOOK=1:
The lexer now recognizes the construction <qualifier>::<symbol> and
transforms it in the following manner (T is the transformation function):

T(<qualifier>::<symbol>) = (*colon-hook* 'T(<symbol>) <qualifier>)

where <qualifier> is a symbol not containing any double-colons.

As the definition is recursive, qualifiers can be nested.
The user can define his own *colon-hook*, to handle qualified names.
By default, "init.scm" defines *colon-hook* as EVAL. Consequently,
the qualifier must denote a Scheme environment, such as one returned
by (interaction-environment). "Init.scm" defines a new syntantic form,
PACKAGE, as a simple example. It is used like this:

(define toto
(package
(define foo 1)
(define bar +)))

foo ==> Error, "foo" undefined
(eval 'foo) ==> Error, "foo" undefined
(eval 'foo toto) ==> 1
toto::foo ==> 1
((eval 'bar toto) 2 (eval 'foo toto)) ==> 3
(toto::bar 2 toto::foo) ==> 3
(eval (bar 2 foo) toto) ==> 3

If the user installs another package infrastructure, he must define
a new 'package' procedure or macro to retain compatibility with supplied
code.

Note: Older versions used ':' as a qualifier. Unfortunately, the use
of ':' as a pseudo-qualifier in existing code (i.e. SLIB) essentially
precludes its use as a real qualifier.

a href target="_tab"

Как открыть окно в новой вкладке?

Никак. По крайней мере, так пишут вот здесь:
http://www.highdots.com/forums/cascading-style-sheets/target-new-tab-233568.html

Вкратце, там сообщают, что это дело пользователя, где ему открывать ссылки. Ещё там же есть ссылка на 10 ошибок вебпрограммиста:
http://www.useit.com/alertbox/9605.html

IrfanView + Ubuntu

IrfanView 4.25 успешно встал на Ubuntu.

Последовательность действий.

1. Копируем из Windows XP mfc42.dll в wine_c/windows/system32. (Старый mfc42 переименовываем в old).
2. Скачиваем и устанавливаем IrfanView.
3. Удаляем файл wine_c/Program Files/i_view32.ini

Всё, можно работать.

Примечания:

1. Легко запомнить последовательность букв в начале слова. "ИРодыФАНполучают от дурацких названий".

2. IrfanView по какой-то непонятной причине ни разу не видит файлы за пределами wine_c.

среда, 17 марта 2010 г.

getElementById + Focus

Сфокусироваться на нужном элементе в IE просто так не получится, надо ставить таймаут:

document.getElementById('test').focus();
setTimeout("document.getElementById('test').select();", 100);


Первая строчка (focus) нужна для Оперы. Иначе Опера выбрать — выберет, но не сфокусируется.

Попасть всплывающим окном в монитор

Задача. Показать пользователю окно рядом с местом, куда он щёлкнул мышкой. Если щёлкнул в середине или вверху экрана — показать окно чуть ниже щелчка. Если щёлкнул в самом низу экрана — наоборот, показать выше щелчка, чтобы оно не спряталось внизу.

Вот кусок живого кода (тестировалось в О9.64, ФФ3.6,ИЕ8 и S4:

// Показывает окно с предложением выбрать путь
function pickPath (pathVar, path, event, obj) {
// Прокрученное пользователем сверху расстояние
var scrY = getScrollXY()[1];
// Расстояние от верха окна браузера
var offY = getMouseXY(event)[1] - scrY;
// Высота окна браузера
var cH = document.body.clientHeight;
if ((offY + 100) < cH)
y = offY + 20 + scrY;
else
y = offY + scrY - 90;
document.getElementById('winPs').style.top = y;
document.getElementById('winPs').style.left = 22;
document.getElementById('winPs').style.display = 'block';
document.getElementById('fieldPs').value = path;
}

// Возвращает абсолютные координаты курсора мыши
function getMouseXY(event) {
if (event.pageX || event.pageY) {
mouseX = event.pageX;
mouseY = event.pageY;
} else {
mouseX = event.clientX + document.body.scrollLeft;
mouseY = event.clientY + document.body.scrollTop;
}
return [mouseX, mouseY];
}

// Возвращает прокрутку экрана
function getScrollXY() {
var scrOfX = 0, scrOfY = 0;
if( typeof( window.pageYOffset ) == 'number' ) {
//Netscape compliant
scrOfY = window.pageYOffset;
scrOfX = window.pageXOffset;
} else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
//DOM compliant
scrOfY = document.body.scrollTop;
scrOfX = document.body.scrollLeft;
} else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
//IE6 standards compliant mode
scrOfY = document.documentElement.scrollTop;
scrOfX = document.documentElement.scrollLeft;
}
return [scrOfX, scrOfY];
}


Примечания.

1. Вначале я определял положение курсора в одну строчку, но та функция не заработала в Опере.

2. Если нужно получить координаты внутри, например, картинки, надо использовать конструкцию типа такой:

y = y - obj.offsetTop;
x = x - obj.offsetLeft;

3. Функцию определения скроллинга я целиком позаимствовал вот отсюда: ссылка. В другом месте видел таблицу, из которой следует, что сократить конструкцию будет непросто.

4. Функцию определения позиций мыши снянул у Дейва: ссылка. По пути копи-паста функция сократилась в три раза, так что, возможно, в ней что-то испортилось.

Архив блога