Idea to recreate my home page was pretty old — old variant was created more than 10 years ago, and had a very primitive design, like many sites of HTML, version 2.

Preparatory work

As on my current hosting I able to host only static files, this restricted my choice of technologies that could be used for implementation. First, I wanted to write HTML generator myself, using something like Perl, but I just remembered, that I saw sites, that was created using Emacs Muse, which I already used for my notes and articles.

Another task for this site was the creation of a new design. As I'm not so good designer/HTML coder, I searched the Internet for good free site designs. During surfing, I found site of Andreas Viklund and took suitable template (I change it at the end, but this was mostly changes in fonts sizes, etc.)

Main parts of design was plugged to Muse without any problem, and I also use it for all my notes and articles, some of them you can find on this site. To create a dynamic parts of pages — menus for navigation between parts of the site, I wrote some amount of code in Elisp.


For generation of site from Muse source files I created configuration file, consisting from several parts, described below.

First we need to initialize the package and set all necessary variables. Some loadable packages aren't used for site generation, but they are used for my other projects. Some variables changes package's behaviour, for example, associating .muse extension with Muse files, set encoding for files & generated html, etc.

(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))

 '(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)

For site generation, I redefined two styles: for HTML & PDF — this allows me to use styles, different from styles, used for other projects.

(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")

These files you can find on my site: HTML — header.tmpl & footer.tmpl, PDF — header.tex & footer.tex. Muse source files also uploaded to site, and you can find them if you replace .html to .muse, for example, for this article this file is ./EmacsMuseMyPage.muse.

You also need to setup project for this site:

(setq muse-project-alist
         (,@(muse-project-alist-dirs "~/projects/my-page-muse") :default "index")
         ,@(muse-project-alist-styles "~/projects/my-page-muse"
         (: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[^/]*$"))))

Generated files are placed in catalog with source files — in my case this is ~/projects/my-page-muse. muse-project-alist-dirs generate list of directories for which given style (my-page-html) will be used. And two last records are used for generation of PDF-files for English & Russian versions of CV (using style my-page-pdf).

For proper generation of links to style files (.css) and images I wrote small function muse-gen-relative-name, that accepts file name (relative to root directory of project) as parameter, and generate correct file name relative to current file:

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

Other function, muse-mp-detect-language, is used for detection of what language is used for current file1:

(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)))))

Structure of navigation menu, generated for every page, is defined by variable my-page-menu, and represented as alist, every element of which consists from language tag, and list of pair, representing file (or directory) name and corresponding title for menu item.

(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")))))

And at the end, here is function generation of menu, that is called for every page in project. This function finds language for current page, gets name of generated file and sets necessary attributes for menu item, that matches to current path (for example, this note is in section "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
                                (lambda (x)
                                  (concat "<li><a href=\"" rel-path (car x)
                                          (if (string-match (concat "/" menu-lang "/" (car x))
                                              "\" class=\"current\""
                                          ">" (cdr x) "</a></li>"))

Besides this, we also need one more function that is used in footer to print last change data for given page:

(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))))

That's all. Everything is pretty simple and doesn't require manual changes in generated pages. Generated files are ready to upload to a server.

1. Implementation of this function isn't optimal and it could fail sometimes, but it could be changed without affecting other code.

Last change: 25.09.2021 14:16

blog comments powered by Disqus