Меню:


Идея переделать свою домашнюю страницу возникла у меня очень давно — старый вариант существовал уже более 10 лет и имел крайне примитивный дизайн, на уровне HTML версии 2.

Подготовительная работа

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

Другой задачей был новый дизайн сайта. Поскольку художник/верстальщик из меня некудышный, я полез в Интернет в поиске подходящих бесплатных шаблонов сайтов. В итоге я набрел на сайт Andreas Viklund'а и взял понравившийся мне шаблон (правда немного пришлось его подкрутить для использования нужных шрифтов и т.п.).

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

Реализация

Для генерации сайта из исходных текстов Muse используется конфигурационный файл, состоящий из нескольких частей, описанных ниже.

Сначала мы инициализируем пакет и выставляем нужные переменные. Среди загружаемых пакетов есть неиспользуемые непосредственно в генерации сайта, но этот же конфигурационный файл у меня используется и для других проектов. А переменные определяют поведение пакета, например, наличие расширения .muse для всех документов, кодировку документов и генерируемого html и т.п.

(require 'muse-mode)
(require 'muse-html)
(require 'muse-colors)
(require 'muse-wiki)
(require 'muse-latex)
(require 'muse-texinfo)
(require 'muse-docbook)
(require 'muse-project)

(add-to-list 'auto-mode-alist '("\\.muse$" . muse-mode))

(custom-set-variables
 '(muse-html-encoding-default (quote utf-8))
 '(muse-html-meta-content-encoding (quote utf-8))
 '(muse-html-charset-default "utf-8")
 '(muse-file-extension "muse")
 '(muse-mode-auto-p nil)
 '(muse-wiki-allow-nonexistent-wikiword nil)
 '(muse-wiki-use-wikiword nil)
 '(muse-ignored-extensions (quote ("bz2" "gz" "[Zz]" "rej" "orig" "png" "hgignore" "gif"
                                   "css" "jpg" "html" "sh" "lftp" "pdf")))
)

(defun my-muse-mode-hook ()
  (auto-fill-mode 1)
  (flyspell-mode 1)
  (footnote-mode 1))
(add-hook 'muse-mode-hook 'my-muse-mode-hook)

Для генерации содержимого сайта я переопределил два стиля — для HTML & PDF, что позволяет мне использовать стили, отличающиеся от стилей, применяемых для других моих проектов — статей и всяческих записей.

(muse-derive-style "my-page-html" "html"
                   :header "~/projects/my-page-muse/header.tmpl"
                   :footer "~/projects/my-page-muse/footer.tmpl")

(muse-derive-style "my-page-pdf" "pdf"
                   :header "~/projects/my-page-muse/header.tex"
                   :footer "~/projects/my-page-muse/footer.tex")

Файлы используемые в этих стилях также лежат на моем сайте: HTML — header.tmpl & footer.tmpl, PDF — header.tex & footer.tex. Исходные файлы Muse также лежат на сайте, и вы можете увидеть их, заменив расширение .html на .muse, например, для данного расказа это будет ./EmacsMuseMyPage.muse.

Затем определяется проект сайта:

(setq muse-project-alist
      `(
        ("my-page"
         (,@(muse-project-alist-dirs "~/projects/my-page-muse") :default "index")
         ,@(muse-project-alist-styles "~/projects/my-page-muse"
                                      "~/projects/my-page-muse"
                                      "my-page-html")
         (:base "my-page-pdf"
                :path "~/projects/my-page-muse/en"
                :include "/alexott-cv-en[^/]*$")
         (:base "my-page-pdf"
                :path "~/projects/my-page-muse/ru"
                :include "/alexott-cv-ru[^/]*$"))))

Сайт выводится в тот же каталог, где находятся исходные файлы (с учетом их расположения в иерархии каталогов проекта) — в данном случае это ~/projects/my-page-muse. muse-project-alist-dirs генерирует список каталогов проекта для которых будет применяться заданный стиль (my-page-html). А две последних записи используются для генерации PDF-версии английского и русского резюме используя стиль my-page-pdf.

Для правильной генерации ссылок на стили и прочие файлы я написал небольшую функцию muse-gen-relative-name, которая принимает в качестве параметра имя файла относительно корневого каталога проекта, и генерирует имя файла относительно текущего файла:

(defun muse-gen-relative-name (name)
  (concat
   (file-name-directory (muse-wiki-resolve-project-page (muse-project)))
   name))

Другая функция, muse-mp-detect-language, используется для определения того, какой язык используется для данного файла1:

(defun muse-mp-detect-language ()
  (let ((lang "NN")
        (cur-dir (file-name-directory (muse-current-file)))
        )
    (let ((smatch (string-match "/\\(ru\\|en\\|de\\)/" cur-dir)))
      (when smatch
        (setq lang (substring cur-dir (+ smatch 1) (+ smatch 3)))))
    lang))

Структура меню, генерируемая для каждой страницы, определяется переменной my-page-menu, и представляет собой ассоциативный список, каждый элемент которого состоит из названия языка и списка пар, представляющих имя файла (или каталога) и соответствующее название пункта меню.

(setq my-page-menu '(("ru" . ( ("index.html" . "Главная")
                               ("cf/" . "Информационная безопасность")
                               ("fp/" . "Функциональное программирование")
                               ("scheme/" . "Scheme")
                               ("lisp/" . "emacs-lisp")
                               ("cpp/" . "C++")
                               ("oss/" . "Open Source Projects")
                               ("emacs/" . "Emacs")
                               ("writings/" . "Статьи")))
                     ("en" . ( ("index.html" . "Main")
                               ("cf/" . "Information Security")
                               ("fp/" . "Functional programming")
                               ("cpp/" . "C++")
                               ("oss/" . "Open Source Projects")
                               ("emacs/" . "Emacs")
                               ("writings/" . "Articles")))))

И собственно функция для генерации меню на каждой странице, входящей в проект. Она определяет язык страницы, получает имя генерируемого файла, и в процессе генерации меню выделяет аттрибутом пункт, соответствующий нужному разделу сайта (например, этот рассказ находится в разделе "Статьи").

(defun muse-mp-generate-menu ()
  (let* ((menu-lang (muse-mp-detect-language))
         (menu-struct (assoc menu-lang my-page-menu))
         (menu-string "")
         (rel-dir (file-name-directory (muse-wiki-resolve-project-page (muse-project))))
         (rel-path (if (> (length rel-dir) 2)   (substring rel-dir 3) ""))
         (cur-path-muse (muse-current-file))
         (cur-path-html (replace-regexp-in-string "\\.muse" ".html" cur-path-muse))
         )
      (when menu-struct
        (let ((menu-list (if (not (null menu-struct)) (cdr menu-struct))))
          (setq menu-string
                (concat "<ul class=\"avmenu\">"
                        (apply #'concat
                               (mapcar
                                (lambda (x)
                                  (concat "<li><a href=\"" rel-path (car x)
                                          (if (string-match (concat "/" menu-lang "/" (car x))
                                                            cur-path-html)
                                              "\" class=\"current\""
                                            "\"")
                                          ">" (cdr x) "</a></li>"))
                                menu-list))
                        "</ul>"))))
      menu-string))

Кроме того, нужна еще одна функция, которая используется в footer для отображения даты последнего изменения страницы:

(defun generate-change-date (file)
  (when (file-exists-p file)
    (let* ((fa (file-attributes file))
           (mod-time (nth 6 fa)))
      (format-time-string "%d.%m.%Y %R" mod-time))))

Вот и все. Все получилось достаточно просто и не требует никаких дополнительных действий по изменению сгенеренного html. Полученные файлы может сразу загружать на сервер.


1. Реализация этой функции не особо оптимальна и может приводить к ошибкам, но ее можно переписать не затрагивая остальной код.

Last change: 05.03.2013 16:54

blog comments powered by Disqus