12/16/2013

Как сделать календарь в LaTeX на год и месяц автоматически?

Это краткая заметка в стиле "как отстрелить себе ногу с помощью LaTeX". Автору этих строк потребовалось сделать себе календарик, в котором отображались бы оповещения о событиях, и чтобы это дело регенерировалось автоматически при смене месяца.

Предисловие

Автор этих строк - человек очень ленивый, но любопытный и обожающий ковырять свой Дебиан на предмет того, как бы сбросить побольше рутины на компьютер. Так появилась LaTeX-реализация организационной системы Getting Things Done, которая выполнена полностью на латехе, вместе с адресной книгой и календарём.

Календарь на LaTeX реализован с помощью пакета расширений calendar [скачать ZIP-файл]. Проблема в том, что месяцы в календаре нужно обновлять самому, а вот это как раз и забывается чаще всего. Поэтому хочется, чтобы дни, месяцы и годы в календарике LaTeX обновлялись автомагически латехом и без вмешательства ленивого и забывчивого автора этих строк.
Нужно сказать, что LaTeX является Turing complete language, то есть на нем можно писать любые программы. Например, можно написать интерпретатор Бейсика, симулятор машины Тьюринга, Mandelbrot with LaTeX и другие программы. То есть на латехе можно писать что угодно. Не всегда это просто (особенно в случае с календарём), но можно.
Пост поделён на две части: сначала немного о возможностях пакета Calendar, а потом про то, как обновление названий месяцев сделать автоматически из LaTeX.

Возможности пакета Calendar в LaTeX

О возможностях пакета Calendar уже говорилось, но тем не менее. Последнюю версию пакета расширений calendar, который можно загрузить отсюда. Файлы примеров этого поста доступны здесь, батарейки в комплекте (пакет calendar там уже есть).

После распаковки всех файлов в преамбуле документа подключаем пакеты:
%%% Turning on the Monthly calendar and Event list
\usepackage{monthly,evntlist,lscape} \parindent=0pt
для календаря на месяц и списка событий, и:
%%% Turning on the Yearly calendar
\usepackage{yearly}
для календаря на год соответсвенно.

Создание событий для календаря

Пакет calendar не просто создаёт календарь, но и позволяет отображать в нём события. Все события хранятся в одном текстовом файле myEvents.cld из которого они дёргаются календарём. Файл событий выглядит так:
%%%%%%% My Personal Calendar
range Essentials/Calendar/year2010 %% What year do we want?
%% ONE-TIME EVENTS
january 28 2010 {Описать GTD} [Описать GTD для блога]
%%%%% RECURRING EVENTS
every Sunday {Еженед. обзор} [Еженед. обзор]
Сначала указываем год в отдельном файле Essentials/Calenda/year2010.cld в котором пишем:
%% Span the whole year here
January 1 2010 to December 31 2010 {The year 2010}
Повторяющиеся события будут определены только в интервале из этого файла.

Список событий из календаря

Часто нужно просто видеть события, приуроченные к календарным датам (особенно если их не так много). Для этого мы пользуемся окружением eventlist, которое предоставляет пакет calendar. Настройки того, как выглядит список событий, хранятся в файле evntlist.sty который можно приукрасить разными значками и иконками.

Чтобы распечатать события между нужными датами, вы просто ставите две даты, между которыми хотите показывать события (хранятся в файле events.cld):
\begin{eventlist} {} {Essentials/Calendar/myEvents}
january 24 2010 to january 30 2010
\end{eventlist}
и собираете LaTeXом документ, получая список календарных событий на это время:



Иконки сделаны пакетами шрифтов marvosym и wasysym.

Календарь на месяц с отображением событий

Пакет calendar может больше - можно создать календарь на месяц и показывать там события. Код такой:
\begin{monthly}
{firstday=1} %% begins with Monday
{Essentials/Calendar/myEvents}
jan 2010
\end{monthly}
Все события в календаре на указанный месяц (январь 2010 в данном случае) берутся из того же файла events.cld, который мы использовали для генерации списка календарных дней на неделю.
Компилируем документ и вот он, календарь на месяц, обновлённый и со вставленными событиями:


Календарь работает с кириллицей, во всяком случае кодировка KOI8-R у него возражений не вызывает.

Календарь на год

Места для отображения событий в календаре на год особенно не много, но возможность сгенерировать годовой календарь, не отходя от кассы выходя из латеха весьма кстати. Код для этого:
\begin{yearly}
 {title= \begin{center} \textbf{\Large The Year 2013}\end{center} \normalsize,firstday=1}
{}
2013
\end{yearly}
После сборки документа годовой календарик будет выглядеть так:



Можно посылать в печать.

Вызов скриптов из LaTeX

С календарём всё сравнительно понятно, и теперь настало время его автоматизировать: хочется, чтобы названия месяцев и дней подставлялись автоматически. Это не такая простая задача, как может показаться. Дело в том, что стандартные команды типа \the\year с пакетом calendar работать не будут, как не получится и подставлять значения из файлов через команду \input.

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

Скрипт на Питоне для генерации файлов календаря

Скрипт просто склеивает строки для латеха, дёргая команду date и вставляя даты куда нужно. В примере ниже показана часть генерации файла календаря на месяц.

Весь латеховский файл представляет собой склеенные строки в переменной out. Вставка символа r в строках out +=r'\begin{landscape}'+'\n' указывает Питону не интерпретировать \b а печатать как есть. Вызов команды date делается через os.popen(cmd), хотя теперь так уже не модно (но тем не менее работает), а модно через subprocess.check_output (но у меня так не получилось).

От полученной строки из команды date отдельно откусывается символ новой строки через .rstrip('\n') и далее сшивается с другими стоками. Результат записывается в файл tmpCalendarMonth.tex, который в свою очередь вставляется в ЛаТеХ через \input{Calendar/tmpCalendarMonth} и обрабатывается при сборке. Скрипт на питоне ниже:
#! /usr/bin/python
import string, os
import commands

kmvStartDir = '.'
kmvDestDir = kmvStartDir+'/Calendar/' #destination directory for graphs

###### Getting dates and months as text using DATE command in Linux ####
cmd='date --date="today" +%Y'
stdout_handle = os.popen(cmd)
kmv_year = stdout_handle.read()
kmv_year = kmv_year.rstrip('\n')
stdout_handle.close()

cmd='date --date="today" +%B'
stdout_handle = os.popen(cmd)
kmv_month = stdout_handle.read()
kmv_month = kmv_month.rstrip('\n')
stdout_handle.close()

cmd='date --date="today" +%e'
stdout_handle = os.popen(cmd)
kmv_day = stdout_handle.read()
kmv_day = kmv_day.rstrip('\n')
stdout_handle.close()

####################################
#### Month Calendar regeneration ###
####################################
kmvCalName = kmvDestDir+'tmpCalendarMonth'

out = '' 
out +=r'\begin{landscape}'+'\n'
out +=r'\begin{monthly}'
out +='\n {firstday=1} \n'
out +='{Calendar/myEvents} \n'
out +=kmv_month+' '+kmv_year+'\n'
out +='\end{monthly}\n'
out +='\end{landscape}\n'

### Output to the Calendar's file ######
kmvCalName+='.tex'
fout=open(kmvCalName,'w')
fout.write(out)
fout.close()
####################################

Скрипт calendar_regenerate.py и все остальные файлы можно взять отсюда. Скрипт написан для того, чтобы быть максимально понятным, а не красивым или эффективным.

Вызов скрипта на Питоне из LaTeX

Вызывать скрипты из латеха можно несколькими способами:
Здесь я приведу второй вариант, как наиболее простой. Для этого мы помещаем питоний скрипт calendar_regenerate.py в тот же каталог, где лежит файл 4myGTD.tex, из которого скрипт будет вызываться. В преамбуле документа пишем:

%%% Python script for calendar regeneration
\immediate\write18{./calendar_regenerate.py}

Чтобы всё это заработало, нужно вызывать LaTeX с параметром -shell-escape который позволяет выполнение внешних скриптов.

Кто такой \write18 и почему так называется?!

Команда \write это низкоуровневая инструкция TeX, которая используется для того, чтобы производить запись в файловые "потоки". ТеХ ссылается на каждый открытый файл не по имени, а по номеру. Поток 18 является особым и зарезервирован для того, чтобы попросить операционную систему что-то выполнить - например, внешний скрипт.
Внимание! Как совершенно справедливо отмечается многими, подобный трюк в сочетании с параметром –shell-escape небезопасен и представляет собой потенциальную дыру в безопасности. Так можно написать вредоносный документ с командой типа \write18{rm -rf ~} в Unix. По этому поводу есть статья (PDF) об этой и других опасных командах в LaTeX.
Команда \immediate приказывает ЛаТеХ выполнить скрипт немедленно, не дожидаясь окончания генерации всего документа. В данном случае это оправдано, так как мы хотим, чтобы в документ вставился уже обновлённый календарь.

Заключение

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

7 комментариев:

  1. Это реально "как отстрелить себе ногу с помощью LaTeX". :) Интересный пост. Кстати, а календарь на рабочий стол кладётся или на принтер идёт? Просто интересно.
    З.Ы.
    сначала немного о возможностях пакета Cacendar, а потом про то, как обновление названий месяцев сделать автоматически из LaTeX
    Cacendar - очепятка. :)

    ОтветитьУдалить
  2. Зачем дёргать из питона команду date?
    http://docs.python.org/2/library/datetime.html

    ОтветитьУдалить
  3. т.е. блок для генерации строки представленной
    kmv_month+' '+kmv_year+'\n'

    будет примерно таким (модификаторы в формате такие же как у команды date)
    from datetime import date
    '{0:%B} {0:%Y}'.format(date.today())

    ОтветитьУдалить
  4. @Basil Orlov комментирует...

    Это реально "как отстрелить себе ногу с помощью LaTeX".

    Так и есть. Кстати, в новой версии латеховского пакета всё сломали, как обычно, и там теперь требуются какие-то экзотические пакеты. Лучшее - враг хорошего...

    Кстати, а календарь на рабочий стол кладётся или на принтер идёт?

    Кому как, мне - обычно на принтер. Это часть моей ЛаТеХ-GTD системы, и, надо сказать, самая стрёмная её часть в плане наворотов. Вообще, там просто адское месиво из латеха, питона, тикля, зима, зотеры (god forbid!), эскьюлайта, графвиза и баш-скриптов. Эталонный опенсорц и настоящий frankensoftware.

    Cacendar - очепятка. :)

    Исправил, спасибо.

    @Alexey Balmashnov комментирует...

    Зачем дёргать из питона команду date?

    Автору было лень читать доки по питону. :-) Их там так много, и они такие раскидистые...

    http://docs.python.org/2/library/datetime.html

    не дочитал я до туда :-) Спасибо, Алексей, за ссылку.

    т.е. блок для генерации строки представленной

    Ну да. Просо я смотрю на питон, как на дешёвый клей для скриптов (чем он, в общем-то, и является), на который к тому же нельзя положиться: питонЪ 3, знаете ли...

    И вообще, в моём случае с некоторых пор в номинации "Quick&Dirty" и лабания скриптов балом правит TCL.

    Убойные плюсы: прост (делает то, что ждёшь: puts $a$b выдаст именно это), продуман (создан профессором computer science, а не студентом-двоешником), со стабильным синтаксисом (привет пистону), с возможностью прикручивания гуёв из коробки (привет вообще всем), с читабельным синтаксисом (привет перлу), мощными регэкспами, вменяемой работой под виндой (привет руби), заточенность под работу со строками и приемлемой скоростью работы (снова привет пистону).

    Из минусов: мало книг и туториалов (зато те, что есть - хороши), убогая документация (вики просто кошмар), слабая реализация функционального стиля (не фатально, но тем не менее).

    ОтветитьУдалить
  5. @virens комментирует...
    Так и есть. Кстати, в новой версии латеховского пакета всё сломали, как обычно, и там теперь требуются какие-то экзотические пакеты. Лучшее - враг хорошего...
    Уже и слышал, и видел. Столкнулся с тем, что свой старый теховский документ не смог собрать. пришлось ковыряться и разбираться что да как. :( Грустно. Такое ощущение, что набижали пионэры и, не осилив что есть, запилили свой ТеХ, ну с блекджеком тоже...

    ОтветитьУдалить
  6. @Basil Orlov комментирует...
    Уже и слышал, и видел. Столкнулся с тем, что свой старый теховский документ не смог собрать.

    Храни старые версии пакетов. Я не шучу: у меня в дропбоксе хранится Calendar и ModernCV - и то, и другое (и не только это), испоганили набежавшие красноглазые пионеры. Можно подумать, они добавили туда что-то прорывное...

    Грустно. Такое ощущение, что набижали пионэры
    Так и есть. Давеча решил обновить поделку ReText, которая рендерит оффлайн маркдауновский текст. И шо ви таки думаете? Красноглазый автор перешёл на Пистон3. Ну не (*&^*&( #$#^@$(@#$%^ ли? Не, ну серьёзно: мне что, ещё один питон нужно тащить в свой гадюшник (2.8 и 2.6 уже есть и ползают)?

    ОтветитьУдалить
  7. Без упоминания emacs тема раскрыта не до конца :) В файле ~/.emacs пишем
    (setq cal-tex-diary t) ; чтобы записи из дневника включались в распечтки
    (setq cal-tex-rules t)
    Вызываем emacs-календарь командой M-x calendar
    Для получение разных стилей календаря нажимаем t f w или t f W, или t w 3. На мой вкус лучше всего -- tw3. Дополнительно удобно иметь распечатку t w 2
    В полученном буфере для печати русского текста, вероятно, придётся заменить заголовок на нечто вроде
    \documentclass[a4paper,11pt]{article}\usepackage[koi8-r]{inputenc} \usepackage[russian,english]{babel}
    и не забыть добавить команду \Russian в начале текста

    ОтветитьУдалить