четверг, января 01, 2009

Некоторые хитрости в использовании xmonad

Некоторое время назад я публиковал здесь статьи по настройке ion3. Всё течёт, всё меняется, и сейчас я использую другой фреймовый оконный менеджер - xmonad. С русской документацией по нему сейчас дело обстоит лучше, чем обстояло с ion3, когда я начал писать о нём. Именно, есть довольно основательная статья xmonad: функциональный оконный менеджер. Так что с вопросами "что такое xmonad" отсылаю туда. Однако, когда есть одна только вводная документация - этого всё-таки недостаточно. Хочется примеров настройки и всяческих вкусностей. И их есть у меня! ;)

Во вводных статьях по xmonad обычно рассматриваются три стандартные "компоновки" (способа автоматического расположения окон): Full, Tall и Mirror Tall. "Контрибы" xmonad содержат ещё довольно много компоновок, однако даже базовые могут использоваться более чем одним способом. Например, компоновку "Tall 1 (1/100) (2/3)" (что означает: одно мастер-окно, занимающее по ширине 2/3 экрана, за раз ширина его может меняться на 1/100) я использую для чтения документов и книг: основную часть экрана занимает окно документа, а сбоку может быть что-то ещё. Конечно, такую компоновку можно сделать и "на ходу" из стандартной Tall несколькими нажатиями (по умолчанию) mod-l, но если есть уже сделанная заготовка - проще обратиться к ней. Итак, "хитрость" первая: делайте "заготовки" из настроенных компоновок, чтобы потом быстро к ним обращаться.

Для того, чтобы удобнее было обращаться к конкретным компоновкам, есть расширение XMonad.Layout.Named. Делаем

import XMonad.Layout.Named

и потом в определении layoutHook описываем компоновки, давая им имена. Например, вместо tiled пишем named "dwmtiled" tiled, где "dwmtiled" - выбираемое вами имя компоновки.

По умолчанию для переключения компоновок используются сочетания mod-space (следующая компоновка) и mod-shift-space (предыдущая). Однако, когда компоновок больше чем 2-3, это становится неудобно. Удобнее переключаться сразу на нужную компоновку. Я использую для этого сочетания клавиш типа mod+буква. Чтобы такое себе устроить, подправьте в xmonad.hs строку с импортом модуля XMonad: вместо "import XMonad" напишите

import XMonad hiding ( (|||) )

Это мы указали, что не хотим использовать оператор ||| (служащий для перечисления компоновок), определённый в модуле XMonad. Зато мы будем использовать одноимённый оператор, определённый в модуле LayoutCombinators. Итак,

import XMonad.Layout.LayoutCombinators

Оператор ||| из LayoutCombinators "умнее", и позволяет переключаться сразу на нужную компоновку. Теперь описываем сочетания клавиш для этого переключения:

...
, ((modMask, xK_d ), sendMessage $ JumpToLayout "dwmtiled")
, ((modMask, xK_m ), sendMessage $ JumpToLayout "mirror")
...

где "dwmtiled", "mirror" - имена соответствующих компоновок.

Однако переключение на указанную компоновку - только побочная задача модуля LayoutCombinators. Главное его назначение состоит, соответственно названию, в том, чтобы комбинировать компоновки. Этот модуль содержит операторы типа ***||**. Такие операторы разбивают экран на две части, в каждой из которых работает своя компоновка. Количество звёздочек слева и справа показывает, в каком отношении разбивать экран (скажем, упомянутый оператор делит экран в отношении 3:2). Операторы с вертикальными чертами (|) делят экран по вертикали, а с наклонными (например, ***//*) - по горизонтали. Операторы, в которых две черты (вертикальные или наклонные), позволяют во время работы изменять соотношение частей экрана (перетаскивая границу мышкой), а операторы с одной чертой (например, */***) - не позволяют.

Одна проблема с LayoutCombinators состоит в том, что для перемещения окон между разными частями экрана стандартные действия (swapUp, swapDown) не работают. Для этого приходится использовать модуль WindowNavigation, который определяет модификатор компоновки windowNavigation и действие Move (с аргументом U/D/L/R, указывающим, куда двигать окно).

Вот пример использования LayoutCombinators:

-- Разделить экран по вертикали в отношении 3:1
onebig = windowNavigation (tile ***|* coltile)
where
-- компоновка для левой части
-- master-окно занимает 3/4 по высоте
tile = Mirror $ Tall 1 (1/100) (3/4)
-- компоновка для правой части
-- располагает все окна в один столбец
coltile = Tall 0 (1/100) (1/2)

Здесь onebig - это компоновка, дающая одному окну большую часть экрана (3/4 по вертикали и 3/4 по горизонтали), а остальные располагающая снизу и справа от него. Кому легче один раз увидеть, чем десять раз прочитать - вот пример использования этой компоновки (заодно это иллюстрация к предыдущей статье).

Ещё одна "хитрость" касается автоматического назначения свойств окнам (manageHook). xmonad по умолчанию делает диалоги "плавающими" (float), и это правильно. Только вот по умолчанию распознаются не все диалоги. В частности, по умолчанию xmonad не считает диалогами всплывающие окна Gimp-а (например, диалог кривых и пр). Однако это можно победить. Для таких окон приложения обычно выставляют свойство окна _NET_WM_WINDOW_TYPE в значение _NET_WM_WINDOW_TYPE_DIALOG. Можно заставить xmonad проверять это свойство:

-- подключаем библиотеки X11
import Graphics.X11.Xlib.Extras
import Foreign.C.Types (CLong)
-- Взять значение свойства окна
getProp :: Atom -> Window -> X (Maybe [CLong])
getProp a w = withDisplay $ \dpy -> io $ getWindowProperty32 dpy a w
-- Эта функция проверяет, выставлено ли свойство окна name в значение value
checkAtom name value = ask >>= \w -> liftX $ do
a <- getAtom name
val <- getAtom value
mbr <- getProp a w
case mbr of
Just [r] -> return $ elem (fromIntegral r) [val]
_ -> return False
-- Эта функция проверяет, является ли окно диалогом
checkDialog = checkAtom "_NET_WM_WINDOW_TYPE" "_NET_WM_WINDOW_TYPE_DIALOG"

Другой "пунктик" - надо ещё распознавать "отрывающиеся" (tear-off) меню. Это тоже можно сделать проверкой значения атома:

checkMenu = checkAtom "_NET_WM_WINDOW_TYPE" "_NET_WM_WINDOW_TYPE_MENU"

Объявляем соответствующие manageHook-и и добавляем их к остальным:

-- Сделать меню плавающими
manageMenus = checkMenu --> doFloat
-- Сделать диалоги плавающими
manageDialogs = checkDialog --> doFloat
-- Добавляем наши функции к остальным
myManageHook = ... <+> manageMenus <+> manageDialogs

Xmonad реализует концепцию виртуальных десктопов (здесь они называются workspaces), как, вобщем, и большинство других оконных менеджеров. Однако известна также другая концепция - теги для окон. Теги используются, например, в dwm и awesome. Их можно использовать и в xmonad. У меня сейчас используются обе концепции параллельно.

Чтобы использовать теги в xmonad, нужно подключить соответствующий модуль:

import XMonad.Actions.TagWindows

Я объявляю несколько функций, для пущей читабельности:

-- переместить окна, помеченные тегом name, на текущий workspace
showtag name = withTaggedGlobalP name shiftHere
-- вкл/выкл тег name для текущего окна
toggletag name = withFocused $ \w -> hasTag name w >>=
(\b -> if b then delTag name w else addTag name w)
-- снять тег name
remtag name = withFocused (delTag name)
-- перейти к следующему окну, помеченному тегом name
nexttagged name = focusDownTaggedGlobal name
-- переместить окна с тегом name с текущего workspace на "misc"
shiftoff name = withTaggedP name (W.shiftWin "misc")

Т.к. я использую несколько тегов, то для объявления сочетаний клавиш для перечисленных действий я ввожу отдельную функцию:

-- Объявить сочетания клавиш для тега tag с клавишей tag
tagkeys mask key tag = [
((mod1Mask, key), showtag tag),
((mask, key), toggletag tag),
((mod3Mask, key), nexttagged tag),
((mask .|. controlMask, key), shiftoff tag)
]

(mod3 у меня находится слева от цифрового ряда клавиатуры). Ну и добавляем эти сочетания к остальным:

...
-- Пометить текущее окно произвольным тегом
, ((modMask, xK_t), tagPrompt defaultXPConfig (\s -> withFocused (addTag s)))
-- Снять произвольный тег
, ((modMask .|. controlMask, xK_t), tagDelPrompt defaultXPConfig)
-- Переместить окна, помеченные произвольным тегом, на текущий workspace
, ((mod1Mask, xK_t), tagPrompt defaultXPConfig (\s -> withTaggedGlobalP s shiftHere))
-- Вкл/выкл тег "mark"
, ((modMask, xK_grave), toggletag "mark")
-- Перейти к следующему окну, помеченному "mark"
, ((mod3Mask, xK_grave), focusDownTaggedGlobal "mark")
...
]
++ (tagkeys modMask xK_exclam "web")
++ (tagkeys modMask xK_numbersign "text")
++ (tagkeys modMask xK_slash "gfx")
++ (tagkeys modMask xK_semicolon "office")
++ (tagkeys modMask xK_colon "docs")
++ (tagkeys modMask xK_question "math")
++ (tagkeys modMask xK_asterisk "files")
++ (tagkeys modMask xK_percent "im")

(у меня typewriter-like раскладка клавиатуры, так что при нажатии цифровых клавиш без шифта получаются знаки препинания).

Для полного счастья надо, чтобы некоторым окнам (отбираемым, например, по классу или заголовку) сразу назначались правильные теги. Стандартного manageHook-а для этого нет, так что приходится изобретать свой. Чтобы было покороче, я просто приведу куски своего xmonad.hs:

import XMonad.Hooks.XPropManage
...
myManageHook = ignoresome <+> (xPropManageHook xPropMatches) <+> manageMenus <+> manageDialogs
ignoresome = composeAll
[ className =? "trayer" --> doIgnore
, className =? "fbpanel" --> doIgnore
, className =? "Plasma" --> doIgnore]
xPropMatches :: [XPropMatch]
xPropMatches = tagclasses ["Epiphany-browser", "Kontact", "Liferea-bin"] "web"
++ tagclasses ["gimp", "f-spot", "Inkscape", "Eog"] "gfx"
++ tagclasses ["gnome-terminal"] "term"
++ tagclasses ["Gedit", "Leafpad", "Gvim"] "text"
++ tagclasses ["Evince"] "docs"
++ tagclasses ["Nautilus"] "files"
++ tagclasses ["Amarok", "Rhythmbox", "Totem"] "media"
++ tagclasses ["Wxmaxima"] "math"
++ moveclasses ["Pidgin"] "im"
++ floatclasses ["Qwerty.py"]
where
ckClass cls = [(wM_CLASS, any (cls==))]
-- добавить тег окну
tag name = pmX (addTag name)
-- добавить тег и переместить окно
moveAndTag name = (\w -> addTag name w >> return (W.shift name))
mkfloat = pmX float
-- пометить тегом все окна с данным классом
tagclasses clss name = [ (ckClass cls, tag name) | cls <- clss ]
-- переместить окна с данным классом на воркспейс ws
moveclasses clss ws = [ (ckClass cls, moveAndTag ws) | cls <- clss ]
floatclasses clss = [ (ckClass cls, mkfloat) | cls <- clss ]

Это, конечно, далеко не все "фишки" xmonad. Однако, я надеюсь, кому-то это может послужить стартовой площадкой :)

вторник, декабря 23, 2008

Все форматы документа из одного исходника: asciidoc сотоварищи

Я уже давно использую asciidoc для написания сколько-нибудь больших текстов. Почти все статьи в этом блоге, включая эту, подготовлены с помощью Asciidoc.

Asciidoc - это транслятор простейшего языка разметки текста в любой другой язык разметки. Разметка asciidoc очень простая, практически вы пишете plain text, только выделяете заголовки знаками = в начале строки, полужирный текст - *звёздочками*, курсив - 'кавычками', итд. Абзацы разделяются пустой строкой. А на выходе может быть всё что угодно, это зависит от так называемого backend-a, поведение которого описывается в конфиге. В поставке доступны бэкенды для xhtml, html4 и docbook. Docbook, в свою очередь, теоретически можно отконвертировать во что угодно.

На днях я готовил доклад для одного семинара, и мне хотелось получить его сразу в нескольких форматах: html и pdf, как минимум. И ещё бы надо к нему презентацию… И хорошо бы план доклада. И, конечно, не хочется для каждого формата готовить текст.

HTML (точнее, xhtml 1.1) делается с помощью asciidoc. Все остальные форматы, теоретически, можно получить из docbook, который можно получить с помощью asciidoc. Только вот на практике мне так и не удалось за полдня заставить ни один из конверторов docbook нормально работать с русскими буквами. Также в комплекте asciidoc есть экспериментальный бэкенд latex, но он как-то странно работает с кусками кода, которые мне нужно поместить в tex-файл в неизменном виде (речь идёт о формулах): половина формул куда-то проглатываются.

Кроме всего прочего, мне нужно в доклад включать фрагменты диалога с консольными программами (в данном случае - с maxima и с R). Так как в ходе подготовки доклада что-то может меняться, неохота каждый раз делать copy-paste из консоли. Надо бы, чтобы в исходник вставлять только запросы к программам - а вызываются программы и вставляется вывод пусть автоматически.

В общем, в итоге я написал скрипт lmaxima.py, который делает следующее: читает входной файл, и копирует его в выходной. Если встречает строку вида "program>> команды", то по пайпу передаёт эти команды указанной программе, и её ответ вставляет в выходной файл. Если встречает строку вида "program|tex>> команды" - то указанные команды оборачивает в функцию tex(). Таким образом, lmaxima.py работает как препроцессор для asciidoc. Одна из тонкостей состоит в том, как вставлять в документ формулы, которые выдаёт maxima. Если выводить надо в html, то формулы пропускаются через tex, и в выходной файл вставляется картинка (строка image:chtoto.png[]). Если же выводить надо в pdf, то lmaxima указывается ключ -i, и в выходной файл вставляется непосредственно tex-код.

Т.к. latex-бэкенд к asciidoc работает странно, пришлось писать свой конвертер из подмножества asciidoc-разметки в tex (благо, основная часть разметки asciidoc очень простая). Называется он у меня vsml.py. Заодно vsml.py умеет следующее:

  • С ключом -c - добавляет в документ оглавление (latex-овская команда \tableofcontents),

  • с ключом -p - "выдирает" из исходника только заголовки, и составляет содержание документа (план доклада, в моём случае),

  • с ключом -b - создаёт исходник для презентации (класс beamer); в презентацию попадают заголовки и картинки.

vsml понимает ещё и некоторые "надстройки" над синтаксисом asciidoc. Так, с помощью строчек "//{" и "//}" (asciidoc их воспринимает как комментарии) можно создавать вложенные куски текста. По умолчанию они выводятся как обычно, однако vsml.py можно задать ключ -l с числовым параметром, и он будет выводить только текст с "уровнем вложенности" не больше заданного; это позволяет оформлять более и менее необязательные части текста, и из одного исходника создавать документы разной степени подробности. А с помощью строчки вида "//.Тут заголовок" можно создавать (под)заголовки, которые не будут видны нигде, кроме презентации.

Конечно, вручную писать все эти команды с ключами каждый раз долго, поэтому я написал небольшой Makefile:

all: report.pdf report.html presentation.pdf plan.pdf
clean:
rm report.pdf report.html presentation.pdf
rm presentation.tex report.asciidoc report.vsml
rm plan.tex plan.pdf
plan.pdf: plan.tex
pdflatex $<
plan.tex: report.vsml
vsml.py -p < $< > $@
report.pdf: report.tex
pdflatex $<
presentation.pdf: presentation.tex
pdflatex $<
report.html: report.asciidoc
asciidoc $<
report.asciidoc: math-report
lmaxima.py $< $@
presentation.tex: report.vsml
vsml.py -b < $< > $@
report.tex: report.vsml
vsml.py < $< > $@
report.vsml: math-report
lmaxima.py -i $< $@


PS: мне тут подсказывают: добавь ещё festival, оно за тебя и доклад прочитает :)

воскресенье, ноября 23, 2008

Создание собственных виджетов в PyGTK с помощью cairo

Свободная библиотека Gtk, как известно, не отличается очень большим выбором виджетов. Но никто не мешает создавать свои собственные.

Gtk, как известно, построена на принципах ООП, что хорошо ложится на объектную модель Python. В данном случае это означает, что наследование виджетов естественным образом соответствует наследованию классов в Питоне. Так, создав класс-потомок gtk.VBox, мы получим виджет со всеми свойствами VBox, и сможем добавлять в него нужную функциональность.

Покажу простейший пример. Пусть мы хотим создать виджет, выглядящий как комбинация gtk.Label и gtk.Entry, т.е. поле для ввода сразу с подписью. Чтобы сделать такое непосредственно средствами gtk, нужно создать gtk.HBox, а в него поместить Label и Entry. Т.е. HBox окажется родительским виджетом для всей конструкции. Вот от него и будем наследоваться:

class LabeledEntry(gtk.HBox):

Но наш виджет довольно сильно отличается от простого HBox, поэтому нужно переопределить инициализатор:


  def __init__(self,label=None):
gtk.HBox.__init__(self) # Вызываем инициализатор родительского класса
self.label = gtk.Label(label) # Создаём текстовую метку с нужной подписью
self.entry = gtk.Entry() # И поле для ввода текста
self.pack_start(self.label, expand=False) # Добавляем label в создаваемый виджет
self.pack_start(self.entry, expand=True) # Поле для ввода - туда же

Теперь можно дописывать методы по собственному усмотрению. Например, логично было бы видеть методы set_text и get_text:

  def get_text(self):
return self.entry.get_text()
  def set_text(self,text):
self.entry.set_text(text)

При желании можно добавить, например, get_label и set_label. Пример использования нашего виджета:

...
entry = LabeledEntry("Enter some text")
...

Таким образом, наследуясь от HBox или VBox, можно создавать виджеты, состоящие из нескольких готовых. Но иногда нужны виджеты, внешне не похожие ни на один из стандартных. И вот тогда выручает то, что все виджеты gtk отрисовываются с помощью Cairo, который имеет весьма простой API.

API этот имеет много общего со многими другими рисовальными API. Прежде всего, нужно получить контекст Cairo - объект, содержащий состояние изображения. Далее для собственно рисования вызываются методы этого объекта. Наиболее часто используемые:

  • cr.move_to(x,y) - переместить графический указатель в нужную точку холста,

  • cr.line_to(x,y) - провести линию от положения указателя до данной точки (указатель сдвинется в указанную точку),

  • cr.path_close() - делает текущую линию замкнутой,

  • cr.rectangle(x,y,w,h) - рисует прямоугольник; задаются координаты левого верхнего угла и размеры,

  • cr.set_source_rgb(r,g,b) - выбрать цвет для рисования; компоненты r,g,b измеряются от 0 до 1,

  • cr.stroke() - нарисовать контур текущей линии (выбранным цветом),

  • cr.fill() - закрасить текущую линию.

Координаты измеряются как обычно - от левого верхнего угла вправо и вниз, в пикселах.

Пусть нам, скажем, нужен виджет, который будет отображать простейшие линейные диаграммы. Должна быть возможность добавлять в него данные, а он должен соответственно перерисовывать диаграмму. Такие виджеты удобнее всего наследовать от gtk.DrawingArea:

  class Diagram(gtk.DrawingArea):

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

  def __init__(self,max=10,color=(0.8,0.8,0.6)):
gtk.DrawingArea.__init__(self)
self.data = [1] # Это будут данные, отображаемые виджетом
self.max = max # Сколько максимум данных будет рисовать виджет
self.color = color # Цвет диаграммы
# Вот это, можно сказать, самое главное: привязываем рисующую процедуру к событию перерисовки виджета
self.connect('expose-event', self.on_expose)

Определяем собственно метод, который будет отрисовывать виджет:

  def on_expose(self, widget, event):

В аргументе widget передаётся сам виджет. Первое, что нам от него нужно - это размеры и положение:

    x,y, width,height,_ = widget.window.get_geometry()

Кроме того, нам понадобится контекст Cairo:

    cr = widget.window.cairo_create()

Вычислим некоторые размеры:

    xpad = 0.03*self.width           # Поля по горизонтали
ypad = 0.07*self.height # И по вертикали
w = float(self.width-2*xpad) # Ширина 'рабочей' части виджета
h = float(self.height-2*ypad) # и высота
M = max(self.data) # Максимум данных - он нужен, чтобы выставить масштаб по оси Y
n = len(self.data) # Количество данных
    cr.rectangle(0,0,self.width,self.height)   # Обозначаем прямоугольник, закрывающий весь виджет
cr.set_source_rgb(1,1,1) # Выбираем белый цвет
cr.fill() # Закрашиваем наш прямоугольник - это будет фон
    cr.move_to(xpad, ypad+h-h*float(self.data[0])/M)  # Ставим указатель в верхний левый угол будущей диаграммы
for x,y in enumerate(self.data[1:]): # Пробегаемся по всем данным
cr.line_to(xpad+w*float(x+1)/(n-1), ypad+h-h*float(y)/M) # Проводим очередной отрезок ломанной
cr.line_to(xpad+w, ypad+h) # Проводим правую границу диаграммы
cr.line_to(xpad,ypad+h) # Теперь нижнюю границу
cr.close_path() # Замыкаем ломанную - это проведёт левую границу диаграммы
cr.set_source_rgb(*self.color) # Выбираем цвет
cr.fill() # Закрашиваем ломанную

Этот метод будет вызываться каждый раз, когда нужно перерисовать виджет. Конечно, стоит иметь ввиду, что если он будет выполняться долго - перерисовка виджета будет тормозить. Так что вычислений и циклов в нём должно быть минимум. Всё, что можно, следует вычислять заранее, или кэшировать.

Ну и допишем метод для добавления данных в диаграмму:

def accept(self,n):
if len(self.data) == self.max:
del self.data[0] # Если данных слишком много - забываем самое старое значение
self.data.append(float(n)) # Добавляем число в список
self.queue_draw() # Этот вызов заставит виджет перерисоваться, т.е. лишний раз вызовет on_expose().

Пример использования:

...
dg = Diagram(max=20)
...
dg.accept(10)
dg.accept(20)
...

понедельник, ноября 10, 2008

Typewriter-like раскладки

По совету http://vonderer.blogspot.com/, решил попробовать использовать для русского языка раскладку пишущей машинки. Главное преимущество (для меня) - в том, что знаки препинания обычно в тексте встречаются гораздо чаще, чем цифры, а в typewriter набирать их становится проще. Заодно точка и запятая получают по отдельной клавише, и буква Ё - более удобное место. Вобщем, действительно, удобно. Правда, на привыкание ушло около недели.

Но кроме русской раскладки есть ещё и английская. Для неё в X-ах не предусмотрено tyewriter-варианта, а хочется, потому что на переключение режима в мозгах требуется слишком много времени (цифры набирать то с шифтом, то без, и знаки препинания скачут по всей клавиатуре). Раскладка Дворака (у которой есть вариант с цифрами на верхнем уровне) - слишком другая, а я не так много набираю англоязычных текстов, чтобы изучать совсем новую раскладку (да ещё и надписи на клавишах будут мешать). Вобщем, сделал я себе typewriter-вариант английской раскладки. Выглядит это так:



Соответствующий код (/usr/share/X11/xkb/symbols/ustw):

partial alphanumeric_keys
xkb_symbols "typewriter" {
include "us(basic)"
name[Group1]= "US - Typewriter";
key <AE01> { [exclam, 1 ] };
key <AE02> { [numbersign,2 ] };
key <AE03> { [slash, 3 ] };
key <AE04> { [semicolon, 4 ] };
key <AE05> { [colon, 5 ] };
key <AE06> { [comma, 6 ] };
key <AE07> { [period, 7 ] };
key <AE08> { [asterisk, 8 ] };
key <AE09> { [question, 9 ] };
key <AE10> { [percent, 0 ] };
key <BKSL> { [parenleft, parenright ] };

key <AC10> { [at, ampersand ] };
key <AB08> { [asciicircum, less ] };
key <AB09> { [dollar, greater ] };
key <AB10> { [bar, backslash ] };
};


Кроме того, ещё с давних пор я использую CapsLock как специальный модификатор, превращающий некоторые буквенные клавиши в стрелки итп. Сейчас ещё захотелось на Shift-Caps повесить переключение такого режима (чтоб в браузере тексты читать, листая кнопками j/k, итп). И ещё захотелось временный переключатель из русской раскладки в английскую - иногда >/< или ещё чего набрать быстро. И, раз уж пошла такая пьянка, чтоб можно было греческие буквы побыстрее набирать (временный переключатель в греческую раскладку) (правда, я не верю, что греки пользуются фонетической раскладкой, которая в иксах под именем gr, ну да это их проблемы).

Итак, текущие мои настройки, если кому интересно.

/usr/share/X11/xkb/symbols/addkeys - мои раскладки:


partial alphanumeric_keys
xkb_symbols "en" {
include "ustw"
name[Group1]= "US - Additional";
key.type[group1]="FOUR_LEVEL";
key <AC01> { [ a, A, Home, Home ] };
key <AD03> { [ e, E, End, End ] };
key <AC05> { [ g, G, Home, End ] };
key <AC06> { [ h, H, Left, Left ] };
key <AC07> { [ j, J, Down, Down ] };
key <AC08> { [ k, K, Up, Up ] };
key <AC09> { [ l, L, Right, Right ] };
key <AC03> { [ d, D, Delete, Delete ] };
key <AD10> { [ p, P, XF86ScrollUp, XF86ScrollUp ] };
key <AB06> { [ n, N, XF86ScrollDown, XF86ScrollDown ] };
include "addkeys(caps_switch)"
};

partial alphanumeric_keys
xkb_symbols "ru" {
include "ru(typewriter)"
name[Group1]= "Russia - Additional";
key.type[group1]="FOUR_LEVEL";
key <AC01> { [ Cyrillic_ef, Cyrillic_EF, Home, Home ] };
key <AD03> { [ Cyrillic_u, Cyrillic_U, End, End ] };
key <AC05> { [ Cyrillic_pe, Cyrillic_PE, Home, End ] };
key <AC06> { [ Cyrillic_er, Cyrillic_ER, Left, Left ] };
key <AC07> { [ Cyrillic_o, Cyrillic_O, Down, Down ] };
key <AC08> { [ Cyrillic_el, Cyrillic_EL, Up, Up ] };
key <AC09> { [ Cyrillic_de, Cyrillic_DE, Right, Right ] };
key <AE11> { [ minus, underscore, emdash, hyphen ] };
key <AE12> { [ equal, plus, notequal, plusminus ] };
key <AC03> { [ Cyrillic_ve, Cyrillic_VE, Delete, Delete ] };
key <AD11> { [ Cyrillic_ha, Cyrillic_HA, bracketleft, braceleft ] };
key <AD12> { [Cyrillic_hardsign,Cyrillic_HARDSIGN, bracketright, braceright ] };
key <AD10> { [ Cyrillic_ze, Cyrillic_ZE, XF86ScrollUp, XF86ScrollUp ] };
key <AB06> { [ Cyrillic_te, Cyrillic_TE, XF86ScrollDown, XF86ScrollDown ] };
include "addkeys(caps_switch)"
};

partial alphanumeric_keys
xkb_symbols "gr" {
include "gr"
name[Group1]= "Greek - Additional";
key.type[group1]="FOUR_LEVEL";
key <AC01> { [ Greek_alpha, Greek_ALPHA, Home, Home ] };
key <AD03> { [ Greek_epsilon, Greek_EPSILON, End, End ] };
key <AC05> { [ Greek_gamma, Greek_GAMMA, Home, End ] };
key <AC06> { [ Greek_eta, Greek_ETA, Left, Left ] };
key <AC07> { [ Greek_xi, Greek_XI, Down, Down ] };
key <AC08> { [ Greek_kappa, Greek_KAPPA, Up, Up ] };
key <AC09> { [ Greek_lambda, Greek_LAMBDA, Right, Right ] };
key <AC03> { [ Greek_delta, Greek_DELTA, Delete, Delete ] };
include "addkeys(caps_switch)"
};

xkb_symbols "caps_switch" {
key <CAPS> {
type[Group1]="ONE_LEVEL",
symbols[Group1] = [ ISO_Level3_Shift ]
};
modifier_map Mod5 { ISO_Level3_Shift };

replace key <II65> {
type[Group1]="ONE_LEVEL",
actions[Group1] = [ SetGroup(group=3) ],
actions[Group2] = [ SetGroup(group=3) ],
actions[Group3] = [ ],
actions[Group4] = [ SetGroup(group=3) ]
};

replace key <I21> {
type[Group1]="ONE_LEVEL",
type[Group2]="ONE_LEVEL",
actions[Group1] = [ SetGroup(group=1) ],
actions[Group2] = [ SetGroup(group=1) ],
actions[Group3] = [ SetGroup(group=1) ],
actions[Group4] = [ ]
};

replace key <RCTL> {
actions[Group1] = [ SetGroup(group=2) ],
actions[Group2] = [ SetGroup(group=1) ],
actions[Group3] = [ SetGroup(group=1) ],
actions[Group4] = [ ],
locks = yes
};
};


Ну и в /etc/X11/xorg.conf:


Option "XkbLayout" "addkeys(en),addkeys(ru),gr"
Option "XkbOptions" "grp_led:caps,compose:ralt"



Рус/лат переключается правым Ctrl, индикация лампочкой Caps. Временный переключатель в английскую раскладку на клавише <I21> (у меня она рядом с левым Ctrl). На клавише <II65> (у меня над <I21>) - временный переключатель в третью раскладку (греческие буквы иногда набрать). По Caps+буква - некоторые спецклавиши: Caps-hjkl - стрелки, Caps-a - Home, Caps-e - End, Caps-g - Home, Caps-G - End. На правом Alt - Compose.

четверг, июня 19, 2008

Deployment и Git

Сперва - что такое deployment?

Буквальный перевод - развертывание. Речь идет о том, чтобы заставить код, написанный разработчиком в своей песочнице, заставить работать в реальных условиях на "боевых серверах". И вот во что это выливается даже в случае с одним разработчиком:

Мой нынешний проект я разрабатываю/тестирую на своем домашнем сервере (dev-сервер, это называется). А недавно выложил на хостинг - это, так сказать, production-сервер (ну, на самом деле, это пока что разновидность тестирования). И тут появляются некоторые ньюансы:

  • на dev- и production- серверах нужны разные настройки кода (например, разные пароли для коннекта к БД);

  • на dev-сервере я продолжаю разработку, добавляю новые фичи и пр. Хотелось бы, чтобы новые фичи и в production-варианте появлялись;

  • на production-сервере вылезают некоторые баги, которые по разным причинам на dev не вылезали, их приходится фиксить. Хотелось бы, чтобы эти же баги были пофиксены и в dev-версии;

  • Т.к. код еще не доведен "до кондиции", при переносе выясняется, что кое-где он написан неуниверсально (зависит от специфики того места, где работает). Для работы production-сервере его приходится после выкладывания обобщать. Конечно, нужно, чтобы и в dev-версии он был не менее общим;

  • Некоторые изменения, сделанные в коде production-версии, всё-таки специфичны именно для данного конкретного хостинга. Они в версии на dev-сервере мне совсем не нужны;

  • Кроме всего этого, я хочу держать актуальную версию кода в публичном месте, чтобы любой желающий мог его скачать.

Несложно догадаться, что при попытке выполнять все эти требования "вручную" - очень быстро запутаешься в трех соснах (то бишь, версиях). Для упрощения deployment-а существует довольно много всяких разных решений. Одно из возможных - использовать Git.

Предположим, код на dev-сервере уже под контролем git. Тогда для разворачивания проекта делаем:

user@production.server:/project$ git init
user@dev.server:~/project$ git push ssh://production.server/project master

Собственно, теперь на production.server в директории /project/ имеем копию кода с dev-сервера. Далее:

user@production.server:/project$ git branch production
user@production.server:/project$ git checkout production

Это мы создали новую ветвь репозитория и переключились в нее. Теперь изменяем настройки, тестируем, фиксим баги… не забываем после каждого логически завершенного изменения делать "git commit -a" - фиксировать изменения в репозитории.

При внесении изменений в код на dev-сервере их, конечно, тоже фиксируем. Когда захотим обновить версию на production, делаем:

user@dev.server:~/project$ git push
user@production.server:/project$ git merge master

— вливаем изменения в ветви master в production. Если нужно применить только последнее изменение в master (последний коммит), вместо git merge делаем git cherry-pick.

Чтобы применять нужные изменения, сделанные на production, к dev-версии, делаем:

user@dev.server:~/project$ git remote add prod ssh://production.server/project

— создаем ссылку на удаленный репозиторий,

user@dev.server:~/project$ git fetch prod/production

— получаем код из ветви production,

user@dev.server:~/project$ git cherry-pick prod/production

— это если нужно применить последнее изменение в production, или

user@dev.server:~/project$ git cherry-pick идентификатор-коммита

— если нужно применить произвольный коммит.

Системы контроля версий. Git.

Решил всё-таки написать некое введение в тему, чтобы бы было куда давать ссылки ;) Примечание: информация расчитана на не очень опытных разработчиков. Профессионалам, думаю, все освещенные здесь вопросы покажутся тривиальными. Товарищам, знакомым с контролем версий, но не знакомым с Git, предлагаю первые разделы пропустить.

Что такое управление версиями и зачем оно?

Итак, предположим, вы разрабатываете программу. Для простоты сначала предположим, что вы разрабатываете ее в одиночку и для собственного удовольствия. Как это выглядит?

Создаем директорию MyApplication, в ней - собственно файлы программы (скажем, *.c). Далее начинается итерационный процесс: написали некоторое количество кода → скомпилировали → запустили → обнаружили ошибки или недоработки → отредактировали исходники → … Всё просто, в целом. Но через некоторое время начинаются нетривиальные вещи. Например:

  • Последнее изменение сделало программу хуже, чем она была до того. Хочется вернуть всё "как было". Хорошо, если не успели закрыть редактор - делаем Undo. А если закрыли?

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

  • Если программа предназначена не только для вас лично, то вам придется делать"релизы", т.е. выпускать заведомо работающие версии программы. Для подготовки релиза вам придется фиксить некоторое количество багов. Это довольно скучный процесс, хочется разбавлять его добавлением новых возможностей в программу (это, как правило, интереснее). Но нельзя: добавление новых фич обязательно добавит багов, и вы никогда не доедете до релиза.

Первое, что приходит в голову для решения всех этих проблем - резервные копии. Скажем, каждую работающую версию программы упаковывать в MyApplication_v0.001.tar.gz, в случае чего - откатываться. Для подготовки к релизу - сделать копию директории MyApplication, в которой только фиксить баги, а в основной - добавлять функционал. При этом подходе через некоторое время разработки у вас появится куча *.tar.gz и не меньше копий рабочей директории. Очень быстро вы начнете путаться, где что.

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

  • Вы добавили в программу (в свою копию) возможность A, а ваш товарищ (в свою) - возможность B. Хотелось бы, чтобы у обоих была версия с обоими возможностями.

  • Вы добавили в программу некоторую функцию, ваш товарищ добавил ее же, но реализовал по-другому. Какую версию выпускать и поддерживать дальше?

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

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

Все эти проблемы призваны решить специальные программы - системы контроля версий (version control systems, vcs).

Общие свойства VCS

Если вы работаете с vcs, то у вас есть место, где хранится вся история изменений в программе - это место называется репозиторий (repository). Для внесения изменений в программу вам нужно получить копию текущей разрабатываемой версии - она называется вашей рабочей копией (working copy). Процесс получения рабочей копии традиционно называется словом "чекаут" (checkout). В случае коллективной разработки, вам придется поддерживать актуальность вашей рабочей копии (проверять, что там остальные наработали) - это обычно называется "апдейт" (update). Чтобы ваши изменения попали в репозиторий, вам нужно сделать коммит (commit) (еще это называется словом checkin). Часто словом commit называют собственно сам набор изменений, зафиксированный этой операцией (более точный термин - changeset). Каждое зафиксированное изменение называют ревизией. Вы можете в любой момент откатить свою рабочую копию к любой предыдущей ревизии - это называется revert.

Для того, чтобы отслеживать такие вещи, как релизы, или варианты рефакторинга программы, или тестировать версии с новыми функциями - в репозитории создаются ветви разработки (branches). Для согласования хода разработки в разных ветках (она может вестись, и обычно ведется, параллельно) время от времени делают слияние веток (merge) - когда к одной ветке применяются те же изменения, что были проделаны в другой. Если какое-то место какого-то файла было по-разному изменено в разных ветках, при слиянии возникает конфликт (merge conflict) - его приходится решать вручную, выбирая один из вариантов или как-то логично их объединяя.

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

Автоматизация всех перечисленных операций - дело VCS.

Централизованные vs распределенные VCS

Всё множество VCS можно разделить на два класса - централизованные и распределенные.

В случае с централизованной VCS репозиторий хранится на одном сервере, и все разработчики работают с ним. Очевидное преимущество: простое управление выпуском релизов и вообще ходом развития программы, раз весь код в одном месте. Очевидный недостаток: если с сервером что-то случится, работа всех разработчиков пропадет (даже в случае регулярных бэкапов - пропадет работа всех разработчиков, скажем, за последнюю неделю). Известные примеры централизованных vcs - CVS, Subversion, Perforce.

В случае с распределенной VCS, каждый разработчик имеет полную копию репозитория и работает с ним, время от времени синхронизируясь с репозиториями остальных. Таким образом, для организации целенаправленной разработки потребуются какие-то не технические, а организационно-административные средства. Зато получаем множество преимуществ:

  • Раз у каждого есть полная копия репозитория, работа всех разработчиков пропасть не может вообще.

  • Часто выполняемые операции - прежде всего, commit - происходят почти мгновенно, т.к. не требуют соединения по сети.

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

  • Т.к. в распределенных VCS предполагается регулярная синхронизация репозиториев, в них гораздо более эффективно реализована операция слияния веток (здесь это одна из базовых операций, в отличие от централизованных VCS, где это делается нечасто).

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

Git

Git - это одна из распределенных VCS (distributed version control system, dvcs). Другими примерами dvcs являются Mercurial, Bazaar, Monotone.

Git выделяется на их фоне главным образом из-за того, что он применяется для разработки ядра Linux (http://www.kernel.org). Собственно, именно для этого Git и разрабатывался. Ядро Linux - весьма немаленький проект, как по объему исходного кода, так и по числу участников, поэтому при его разработке большое внимание было уделено производительности и легкости слияния веток.

Далее я привожу часто используемые команды git. Я опускаю кучу тонкостей и многие возможности Git. Всё это можно посмотреть в документации (git help имя-команды, например git help commit). Здесь - скорее отправная точка, свод основных возможностей Git.

Для начала работы с Git вам понадобится репозиторий. Его можно либо создать с нуля, либо клонировать какой-нибудь существующий репозиторий.

$ cd project
$ git init

— создаем пустой репозиторий.

$ git clone git://remote.server/repo.git

— клонируем существующий репозиторий. Кроме собственного протокола, git поддерживает http://, ssh:// и пр.

При создании репозитория в его корневой директории появляется скрытая директория .git - она содержит собственно репозиторий, т.е. историю версий итп. Все остальное содержимое директории проекта - это ваша рабочая копия.

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

$ git add имя-файла-или-директории

Некоторые файлы не принято держать под контролем версий. Прежде всего, это перегенерируемые файлы, временные файлы, файлы журналов итп. Чтобы Git их вообще не замечал, нужно в корневой директории проекта создать файл .gitignore, и в него вписать список игнорируемых файлов, по одному на строку. Можно использовать маски - например, *.pyc. Сам файл .gitignore нужно добавить в репозиторий (git add .gitignore).

Чтобы Git учел изменения в файле, его нужно заново добавить (git add).

Коммит (фиксация изменений в репозитории) делается командой

$ git commit

При этом вам будет предложено ввести "сообщение коммита" (commit message) - пояснение, что вы изменили и зачем.

Чтобы предварительно учесть все изменения в файлах, нужно добавить ключ -a:

$ git commit -a

Если сразу после коммита вы обнаружили, что что-то забыли включить в него, можно поправить:

$ git commit --amend      # Добавить последние изменения в последний коммит, "заодно".

Особенность Git состоит в том, что ревизии (коммиты) обозначаются не номерами, как, например, в svn или cvs, а 40-значными идентификаторами типа a21b96…. Идентификаторы можно сокращать до первых нескольких знаков, лишь бы они однозначно определяли коммит. Кроме того, если нужный вам коммит помечен тегом, то можно использовать имя тега вместо идентификатора коммита. На последний коммит в текущей ветке можно также ссылаться по имени HEAD, на предпоследний - HEAD^, пред-предпоследний - HEAD^^ итп. Существуют и другие способы обозначить нужный коммит, подробности см. в документации.

Увидеть, какие файлы были изменены, добавлены итп после последнего коммита можно командой

$ git status

Сами изменения, сделанные с момента последнего коммита, можно посмотреть командой

$ git diff

У этой команды есть варианты: git diff <идентификатор> - посмотреть изменения с указанного коммита по текущее состояние, git diff <идентификатор1> <идентификатор2> - различия между двумя ревизиями.

Посмотреть журнал последних коммитов - git log.

Если вы создавали свой репозиторий как клон другого, то загрузить изменения исходного репозитория к себе можно командой

$ git pull

Если вам разрешено вносить изменения в исходный репозиторий (это называется push access, предполагает договоренность с владельцем репозитория, и, в зависимости от протокола доступа (ssh или еще что-то), какие-то пароли итп), это делается командой

$ git push

При этом ваши изменения вливаются в репозиторий, который вы клонировали.

Пометить только что созданный коммит можно командой

$ git tag имя-тега

Любой другой коммит помечается так:

$ git tag имя-тега идентификатор-коммита

Посмотреть список веток в репозитории -

$ git branch

(изначально создается одна ветка под названием master).

Создать новую ветку -

$ git branch имя-ветки

или

$ git branch имя-ветки идентификатор-коммита-начинающего-ветку

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

$ git checkout имя-ветки

С тем же успехом этой командой можно переключиться на любой коммит - git checkout идентификатор-коммита.

Влить изменения из другой ветки в текущую -

$ git merge имя-ветки-с-которой-сливаем

Можно взять из другой ветки один конкретный коммит (например, багофикс) -

$ git cherry-pick идентификатор-коммита

воскресенье, июня 15, 2008

Всякое разное: Drupal, Git, Django...

Давно я что-то не писал постов… За это время в голове много чего накопилось, теперь попытаюсь изложить.

Drupal

Вот, за прошедшее время успел более-менее поглубже изучить эту штуку. Кто не слышал - это CMS, а точнее, CMF (content management framework) такой. Я когда-то давно на него смотрел и даже сайты на нем делал, но по принципу "поставил движок, настроил - сдал". Недавно вот чуть посложнее сайты делать пришлось, даже модули к нему писал. Вобщем, drupal хоть и на PHP, но мне понравился ;) Чем именно:

  • посмотрел код - на редкость для php-приложений чистый и понятный;

  • система прав на основе ролей;

  • куча модулей, под каждую задачу, прямо unix-way;

  • эти модули на удивление хорошо дружат меджу собой, конфликтов почти нет;

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

  • удивительно вменяемое русскоязычное сообщество на drupal.ru.

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

Git

До недавнего времени я весь свой программный код хранил в svn. Впрочем, это в основном были совсем небольшие программки. А тут появился на горизонте некий более крупный проект, который предполагалось разрабатывать командой (по крайней мере - не в одиночку). И именно в этот момент появился пост Ивана Сагалаева - жалоба на проблемы с merge в subversion. Мне почему-то сразу расхотелось использовать svn :) Стал смотреть на альтернативы, благо их довольно много. Git приглянулся во-первых своей распределенностью со всеми вытекающими, во-вторых - это мейнстрим (среди dvcs), что ни говори ;). Вобщем, стал изучать. Больше всего понравилось:

  • репозиторий создается фактически одной командой (git init), второй командой в него добавляются файлы (git add .), ну и третьей делается первый коммит (git commit). Получается быстрее, чем с subversion, так что имеет смысл использовать его даже для программы из пары файликов или конфига.

  • собственно факт распределенности, git получается удобнее использовать, когда команда разбросана по городу и не у всех не всегда даже интернет есть.

  • графическая морда, gitk, хорошо показывает ветви, коммиты и всё остальное.

Вобщем, перевел я весь свой код на git, благо тулза для импорта из svn в git есть (git-svn, так и называется).

Django

Эту вещь я когда-то уже тоже изучал, и даже что-то писал, но уже даже не помню, что писал :) Блог, кажется, по традиции :) Сейчас вот изучаю заново (в придачу, с предыдущего подхода многое в django успело поменяться). Кто еще про django не слышал (а тут такие есть? :D) - это фреймворк (т.е., по большому счету, набор библиотек + определенный способ их использования) для создания веб-приложений на python.

Сейчас активно пишу на django что-то типа groupware, назвал пока незамысловато - Projects (надо будет еще придумать подходящее название). Описание тут: http://iportnov.ru/projects/projects.

Еще до этой штуки стал писать библиотечку, которую пока назвал HMS (hooks modules system). Это, собственно, эксперимент: питоновский пакет, с помощью которого в любое питон-приложение можно добавить систему модулей, работающую "примерно как в друпале" (см. выше, про хуки). По-моему, оно довольно красиво смотрится в связке с Django. Сейчас у меня эта HMS входит в состав Projects, хотя пока и мало используется там (я пока что в ядре не все функции написал, не до модулей пока).

Еще в комплекте с Projects у меня есть pygit - веб-интерфейс к Git, написанный на django. При желании его можно почти безболезненно выдернуть из Projects и использовать в других проектах. Для связи с Git использует пакет GitPython. Вот только в этом gitpython есть пока баги, и тормозит он в некоторых местах… Ну, баги, надеюсь, скоро починят, а тормоза можно и стороной обойти.