1 ;;; muse-journal.el --- keep and publish a journal
3 ;; Copyright (C) 2004, 2005 Free Software Foundation, Inc.
5 ;; This file is not part of GNU Emacs.
7 ;; This is free software; you can redistribute it and/or modify it under
8 ;; the terms of the GNU General Public License as published by the Free
9 ;; Software Foundation; either version 2, or (at your option) any later
12 ;; This is distributed in the hope that it will be useful, but WITHOUT
13 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 ;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with GNU Emacs; see the file COPYING. If not, write to the
19 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 ;; Boston, MA 02110-1301, USA.
24 ;; The module facilitates the keeping and publication of a journal.
25 ;; When publishing to HTML, it assumes the form of a web log, or blog.
27 ;; The input format for each entry is as follows:
29 ;; * 20040317: Title of entry
31 ;; Text for the entry.
34 ;; "You know who you are. It comes down to a simple gut check: You
35 ;; either love what you do or you don't. Period." -- P. Bronson
38 ;; The "qotd", or Quote of the Day, is entirely optional. When
39 ;; generated to HTML, this entry is rendered as:
41 ;; <div class="entry">
42 ;; <div class="entry-qotd">
43 ;; <h3>Quote of the Day:</h3>
44 ;; <p>"You know who you are. It comes down to a simple gut
45 ;; check: You either love what you do or you don't. Period."
48 ;; <div class="entry-body">
49 ;; <div class="entry-head">
50 ;; <div class="entry-date">
51 ;; <span class="date">March 17, 2004</span>
53 ;; <div class="entry-title">
54 ;; <h2>Title of entry</h2>
57 ;; <div class="entry-text">
58 ;; <p>Text for the entry.</p>
63 ;; The plurality of "div" tags makes it possible to display the
64 ;; entries in any form you wish, using a CSS style.
66 ;; Also, an .RDF file can be generated from your journal by publishing
67 ;; it with the "rdf" style. It uses the first two sentences of the
68 ;; first paragraph of each entry as its "description", and
69 ;; autogenerates tags for linking to the various entries.
75 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
77 ;; Muse Journal Publishing
79 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
81 (require 'muse-publish
)
86 (defgroup muse-journal nil
87 "Rules for transforming a journal into its final form."
90 (defcustom muse-journal-heading-regexp
91 "\\(?:\\([0-9]+\\)\\(?:: \\)?\\)?\\(.+?\\)?"
92 "A regexp that matches a journal heading.
93 Paren group 1 is the ISO date, group 2 is the optional category,
94 and group 3 is the optional heading for the entry."
98 (defcustom muse-journal-date-format
"%a, %e %b %Y"
99 "Date format to use for journal entries."
101 :group
'muse-journal
)
103 (defcustom muse-journal-html-heading-regexp
104 (concat "^<h2[^>\n]*>" muse-journal-heading-regexp
"</h2>$")
105 "A regexp that matches a journal heading from an HTML document.
106 Paren group 1 is the ISO date, group 2 is the optional category,
107 and group 3 is the optional heading for the entry."
109 :group
'muse-journal
)
111 (defcustom muse-journal-html-entry-template
112 "<div class=\"entry\">
113 <a name=\"%anchor%\" style=\"text-decoration: none\"> </a>
114 <div class=\"entry-body\">
115 <div class=\"entry-head\">
116 <div class=\"entry-date\">
117 <span class=\"date\">%date%</span>
119 <div class=\"entry-title\">
123 <div class=\"entry-text\">
124 <div class=\"entry-qotd\">
131 "Template used to publish individual journal entries as HTML."
133 :group
'muse-journal
)
135 (defcustom muse-journal-latex-section
136 "\\section*{%title% \\hfill {\\normalsize %date%}}
137 \\addcontentsline{toc}{chapter}{%title%}"
138 "Template used to publish a LaTeX section."
140 :group
'muse-journal
)
142 (defcustom muse-journal-latex-subsection
143 "\\subsection*{%title%}
144 \\addcontentsline{toc}{section}{%title%}"
145 "Template used to publish a LaTeX subsection."
147 :group
'muse-journal
)
149 (defcustom muse-journal-latex-markup-tags
150 '(("qotd" t nil muse-journal-latex-qotd-tag
))
151 "A list of tag specifications, for specially marking up LaTeX.
152 See `muse-publish-markup-tags' for more info."
153 :type
'(repeat (list (string :tag
"Markup tag")
154 (boolean :tag
"Expect closing tag" :value t
)
155 (boolean :tag
"Parse attributes" :value nil
)
157 :group
'muse-journal
)
159 ;; FIXME: This doesn't appear to be used.
160 (defun muse-journal-generate-pages ()
161 (let ((output-dir (muse-style-element :path
)))
162 (goto-char (point-min))
163 (while (re-search-forward muse-journal-heading-regexp nil t
)
164 (let* ((date (match-string 1))
165 (category (match-string 1))
166 (category-file (concat output-dir category
"/index.html"))
167 (heading (match-string 1)))
170 (defcustom muse-journal-rdf-extension
".rdf"
171 "Default file extension for publishing RDF (RSS 1.0) files."
173 :group
'muse-journal
)
175 (defcustom muse-journal-rdf-base-url
""
176 "The base URL of the website referenced by the RDF file."
178 :group
'muse-journal
)
180 (defcustom muse-journal-rdf-header
181 "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"
182 xmlns=\"http://purl.org/rss/1.0/\"
183 xmlns:dc=\"http://purl.org/dc/elements/1.1/\">
184 <channel rdf:about=\"<lisp>(concat (muse-style-element :base-url)
185 (muse-publish-link-name))</lisp>\">
186 <title><lisp>(muse-publishing-directive \"title\")</lisp></title>
187 <link><lisp>(concat (muse-style-element :base-url)
188 (concat (muse-page-name)
189 muse-html-extension))</lisp></link>
190 <description><lisp>(muse-publishing-directive \"desc\")</lisp></description>
193 <rdf:li resource=\"<lisp>
194 (concat (muse-style-element :base-url)
195 (concat (muse-page-name)
196 muse-html-extension))</lisp>\"/>
200 "Header used for publishing RDF (RSS 1.0) files.
201 This may be text or a filename."
203 :group
'muse-journal
)
205 (defcustom muse-journal-rdf-footer
207 "Footer used for publishing RDF (RSS 1.0) files.
208 This may be text or a filename."
210 :group
'muse-journal
)
212 (defcustom muse-journal-rdf-date-format
214 "Date format to use for RDF entries."
216 :group
'muse-journal
)
218 (defcustom muse-journal-rdf-entry-template
219 " <item rdf:about=\"%link%#%anchor%\">
220 <title>%title%</title>
224 <link>%link%#%anchor%</link>
225 <dc:date>%date%</dc:date>
226 <dc:creator>%maintainer%</dc:creator>
228 "Template used to publish individual journal entries as RDF."
230 :group
'muse-journal
)
232 (defcustom muse-journal-rdf-summarize-entries t
233 "If non-nil, include only summaries in the RDF file, not the full data."
235 :group
'muse-journal
)
237 (defcustom muse-journal-rss-extension
".xml"
238 "Default file extension for publishing RSS 2.0 files."
240 :group
'muse-journal
)
242 (defcustom muse-journal-rss-base-url
""
243 "The base URL of the website referenced by the RSS file."
245 :group
'muse-journal
)
247 (defcustom muse-journal-rss-header
248 "<\?xml version=\"1.0\" encoding=\"<lisp>
249 (muse-html-encoding)</lisp>\"?>
250 <rss version=\"2.0\">
252 <title><lisp>(muse-publishing-directive \"title\")</lisp></title>
253 <link><lisp>(concat (muse-style-element :base-url)
254 (concat (muse-page-name)
255 muse-html-extension))</lisp></link>
256 <description><lisp>(muse-publishing-directive \"desc\")</lisp></description>
257 <language>en-us</language>
258 <generator>Emacs Muse</generator>"
259 "Header used for publishing RSS 2.0 files. This may be text or a filename."
261 :group
'muse-journal
)
263 (defcustom muse-journal-rss-footer
266 "Footer used for publishing RSS 2.0 files. This may be text or a filename."
268 :group
'muse-journal
)
270 (defcustom muse-journal-rss-date-format
271 "%a, %d %b %Y %H:%M:%S %Z"
272 "Date format to use for RSS 2.0 entries."
274 :group
'muse-journal
)
276 (defcustom muse-journal-rss-entry-template
278 <title>%title%</title>
279 <link>%link%#%anchor%</link>
280 <description>%desc%</description>
281 <author><lisp>(muse-publishing-directive \"author\")</lisp></author>
282 <pubDate>%date%</pubDate>
283 <guid>%link%#%anchor%</guid>
286 "Template used to publish individual journal entries as RSS 2.0."
288 :group
'muse-journal
)
290 (defcustom muse-journal-rss-enclosure-types-alist
291 '(("mp3" .
"audio/mpeg"))
292 "File types that are accepted as RSS enclosures.
293 This is an alist that maps file extension to content type.
294 Useful for podcasting."
295 :type
'(alist :key-type string
:value-type string
)
296 :group
'muse-journal
)
298 (defcustom muse-journal-rss-summarize-entries nil
299 "If non-nil, include only summaries in the RSS file, not the full data.
300 Many RSS subscribers find this annoying."
302 :group
'muse-journal
)
304 (defcustom muse-journal-rss-markup-regexps
305 '((10000 muse-explicit-link-regexp
0 "\\2"))
306 "List of markup rules for publishing a Muse journal page to RSS 2.0.
307 For more information on the structure of this list, see
308 `muse-publish-markup-regexps'."
309 :type
'(repeat (choice
310 (list :tag
"Markup rule"
312 (choice regexp symbol
)
314 (choice string function symbol
))
316 :group
'muse-journal
)
318 (defcustom muse-journal-rss-markup-functions
322 "An alist of style types to custom functions for that kind of text.
323 For more on the structure of this list, see
324 `muse-publish-markup-functions'."
325 :type
'(alist :key-type symbol
:value-type function
)
326 :group
'muse-journal
)
328 (defun muse-journal-anchorize-title (title)
330 (if (string-match "(" title
)
331 (setq title
(substring title
0 (match-beginning 0))))
332 (if (string-match "<[^>]+>" title
)
333 (setq title
(replace-match "" nil nil title
)))
334 (downcase (muse-replace-regexp-in-string "[^a-zA-Z0-9_]" "" title
))))
336 (defun muse-journal-sort-entries (&optional direction
)
342 (if (re-search-forward "^\\* [0-9]+" nil t
)
343 (goto-char (match-beginning 0))
344 (goto-char (point-max)))))
347 (if (re-search-forward "^\\* [0-9]+" nil t
)
348 (goto-char (1- (match-beginning 0)))
349 (goto-char (point-max)))))
357 (defun muse-journal-html-munge-buffer ()
358 (goto-char (point-min))
359 (let ((heading-regexp muse-journal-html-heading-regexp
)
360 (inhibit-read-only t
))
361 (while (re-search-forward heading-regexp nil t
)
362 (let* ((date (match-string 1))
364 (title (match-string 2))
367 (delete-region (match-beginning 0) (match-end 0))
370 (while (string-match "\\(^<[^>]+>\\|<[^>]+>$\\)" clean-title
)
371 (setq clean-title
(replace-match "" nil nil clean-title
)))))
375 (concat "\\`\\([1-9][0-9][0-9][0-9]\\)[./]?"
376 "\\([0-1][0-9]\\)[./]?\\([0-3][0-9]\\)") date
))
380 (string-to-number (match-string 3 date
))
381 (string-to-number (match-string 2 date
))
382 (string-to-number (match-string 1 date
))
384 date
(concat (format-time-string
385 muse-journal-date-format datestamp
)
386 (substring date
(match-end 0))))))
389 (point) (if (re-search-forward
390 (concat "\\(^<hr>$\\|"
391 heading-regexp
"\\)") nil t
)
394 (goto-char (point-max))
395 (while (and (not (bobp))
396 (eq ?\
(char-syntax (char-before))))
398 (goto-char (point-min))
399 (while (and (not (eobp))
400 (eq ?\
(char-syntax (char-after))))
403 (when (search-forward "<qotd>" nil t
)
404 (let ((tag-beg (match-beginning 0))
406 (re-search-forward "</qotd>\n*")
407 (setq qotd
(buffer-substring-no-properties
408 beg
(match-beginning 0)))
409 (delete-region tag-beg
(match-end 0)))))
410 (setq text
(buffer-string))
411 (delete-region (point-min) (point-max))
412 (let ((entry muse-journal-html-entry-template
))
413 (while (string-match "%date%" entry
)
414 (setq entry
(replace-match (or date
"") nil t entry
)))
415 (while (string-match "%title%" entry
)
416 (setq entry
(replace-match (or title
" ") nil t entry
)))
417 (while (string-match "%anchor%" entry
)
418 (setq entry
(replace-match
419 (muse-journal-anchorize-title
420 (or clean-title orig-date
))
422 (while (string-match "%qotd%" entry
)
423 (setq entry
(replace-match (or qotd
"") nil t entry
)))
424 (while (string-match "%text%" entry
)
425 (setq entry
(replace-match text nil t entry
)))
428 (goto-char (point-min))
429 (search-forward "<div class=\"entry-qotd\">")
430 (let ((beg (match-beginning 0)))
431 (re-search-forward "</div>\n*")
432 (delete-region beg
(point))))))))))
434 (defun muse-journal-latex-munge-buffer ()
435 (goto-char (point-min))
436 (let ((heading-regexp
437 (concat "^" (regexp-quote (muse-markup-text 'section
))
438 muse-journal-heading-regexp
439 (regexp-quote (muse-markup-text 'section-end
)) "$"))
440 (inhibit-read-only t
))
441 (when (re-search-forward heading-regexp nil t
)
442 (goto-char (match-beginning 0))
446 (if (re-search-forward heading-regexp nil t
)
447 (goto-char (match-beginning 0))
448 (goto-char (point-max)))))
451 (if (re-search-forward heading-regexp nil t
)
452 (goto-char (1- (match-beginning 0)))
453 (goto-char (point-max)))))
460 (while (re-search-forward heading-regexp nil t
)
461 (let ((date (match-string 1))
462 (title (match-string 2))
463 ;; FIXME: Nothing is done with qotd
468 (concat "\\([1-9][0-9][0-9][0-9]\\)[./]?"
469 "\\([0-1][0-9]\\)[./]?\\([0-3][0-9]\\)") date
))
470 (setq date
(encode-time
472 (string-to-number (match-string 3 date
))
473 (string-to-number (match-string 2 date
))
474 (string-to-number (match-string 1 date
))
476 date
(format-time-string
477 muse-journal-date-format date
))))
479 (setq section muse-journal-latex-section
)
480 (while (string-match "%title%" section
)
481 (setq section
(replace-match (or title
"Untitled")
483 (while (string-match "%date%" section
)
484 (setq section
(replace-match (or date
"") nil t section
))))
485 (replace-match section nil t
))))
486 (goto-char (point-min))
487 (let ((subheading-regexp
488 (concat "^" (regexp-quote (muse-markup-text 'subsection
))
490 (regexp-quote (muse-markup-text 'subsection-end
)) "$"))
491 (inhibit-read-only t
))
492 (while (re-search-forward subheading-regexp nil t
)
493 (let ((subsection muse-journal-latex-subsection
))
495 (let ((title (match-string 1)))
496 (while (string-match "%title%" subsection
)
497 (setq subsection
(replace-match title nil t subsection
)))))
498 (replace-match subsection nil t
)))))
500 (defun muse-journal-latex-qotd-tag (beg end
)
502 (muse-insert-markup (muse-markup-text 'begin-quote
))
504 (muse-insert-markup (muse-markup-text 'end-quote
)))
506 (defun muse-journal-rss-munge-buffer ()
507 (goto-char (point-min))
508 (let ((heading-regexp (concat "^\\* " muse-journal-heading-regexp
"$"))
509 (inhibit-read-only t
))
510 (while (re-search-forward heading-regexp nil t
)
511 (let* ((date (match-string 1))
513 (title (match-string 2))
514 ;; FIXME: Nothing is done with qotd
518 (if (string-match muse-explicit-link-regexp title
)
519 (setq enclosure
(match-string 1 title
)
520 title
(match-string 2 title
)))))
524 (concat "\\([1-9][0-9][0-9][0-9]\\)[./]?"
525 "\\([0-1][0-9]\\)[./]?\\([0-3][0-9]\\)") date
))
526 (setq date
(encode-time 0 0 0
527 (string-to-number (match-string 3 date
))
528 (string-to-number (match-string 2 date
))
529 (string-to-number (match-string 1 date
))
531 date
(format-time-string
532 (muse-style-element :date-format
) date
))))
536 (if (re-search-forward heading-regexp nil t
)
538 (if (re-search-forward "^Footnotes:" nil t
)
541 (goto-char (point-min))
542 (delete-region (point) (muse-line-end-position))
543 (re-search-forward "</qotd>\n+" nil t
)
544 (while (and (char-after)
545 (eq ?\
(char-syntax (char-after))))
548 (if (muse-style-element :summarize
)
551 (setq desc
(concat (buffer-substring beg
(point)) "...")))
553 (muse-publish-markup-buffer "rss-entry" "html")
554 (goto-char (point-min))
555 (re-search-forward "Page published by Emacs Muse")
556 (goto-char (muse-line-end-position))
558 (re-search-forward "Page published by Emacs Muse")
559 (goto-char (muse-line-beginning-position))
560 (setq desc
(concat "<![CDATA[" (buffer-substring beg
(point))
562 (delete-region (point-min) (point-max))
563 (let ((entry (muse-style-element :entry-template
)))
564 (while (string-match "%date%" entry
)
565 (setq entry
(replace-match (or date
"") nil t entry
)))
566 (while (string-match "%title%" entry
)
567 (setq entry
(replace-match (or title
"Untitled") nil t entry
)))
568 (while (string-match "%desc%" entry
)
569 (setq entry
(replace-match desc nil t entry
)))
570 (while (string-match "%enclosure%" entry
)
578 "<enclosure url=\"%s\" %stype=\"%s\"/>"
579 (if (string-match "//" enclosure
)
581 (concat (muse-style-element :base-url
)
584 (expand-file-name enclosure
585 (muse-style-element :path
))))
586 (if (file-readable-p file
)
587 (format "length=\"%d\" "
588 (nth 7 (file-attributes file
)))
590 (if (string-match "\\.\\([^.]+\\)$" enclosure
)
591 (let* ((ext (match-string 1 enclosure
))
594 ext muse-journal-rss-enclosure-types-alist
)))
597 "application/octet-stream"))))))
599 (while (string-match "%link%" entry
)
600 (setq entry
(replace-match
601 (concat (muse-style-element :base-url
)
602 (concat (muse-page-name)
603 muse-html-extension
))
605 (while (string-match "%anchor%" entry
)
606 (setq entry
(replace-match
607 (muse-journal-anchorize-title (or title orig-date
))
609 (while (string-match "%maintainer%" entry
)
610 (setq entry
(replace-match
611 (or (muse-style-element :maintainer
)
612 (concat "webmaster@" (system-name)))
616 (delete-region (point) (point-max)))))
618 (unless (assoc "journal-html" muse-publishing-styles
)
619 (muse-derive-style "journal-html" "html"
620 :before-end
'muse-journal-html-munge-buffer
)
622 (muse-derive-style "journal-xhtml" "xhtml"
623 :before-end
'muse-journal-html-munge-buffer
)
625 (muse-derive-style "journal-latex" "latex"
626 :tags
'muse-journal-latex-markup-tags
627 :before-end
'muse-journal-latex-munge-buffer
)
629 (muse-derive-style "journal-pdf" "pdf"
630 :tags
'muse-journal-latex-markup-tags
631 :before-end
'muse-journal-latex-munge-buffer
)
633 (muse-derive-style "journal-book-latex" "book-latex"
635 :tags
'muse-journal-latex-markup-tags
636 :before-end
'muse-journal-latex-munge-buffer
)
638 (muse-derive-style "journal-book-pdf" "book-pdf"
640 :tags
'muse-journal-latex-markup-tags
641 :before-end
'muse-journal-latex-munge-buffer
)
643 (muse-define-style "journal-rdf"
644 :suffix
'muse-journal-rdf-extension
645 :regexps
'muse-journal-rss-markup-regexps
646 :functions
'muse-journal-rss-markup-functions
647 :before
'muse-journal-rss-munge-buffer
648 :header
'muse-journal-rdf-header
649 :footer
'muse-journal-rdf-footer
650 :date-format
'muse-journal-rdf-date-format
651 :entry-template
'muse-journal-rdf-entry-template
652 :base-url
'muse-journal-rdf-base-url
653 :summarize
'muse-journal-rdf-summarize-entries
)
655 (muse-define-style "journal-rss"
656 :suffix
'muse-journal-rss-extension
657 :regexps
'muse-journal-rss-markup-regexps
658 :functions
'muse-journal-rss-markup-functions
659 :before
'muse-journal-rss-munge-buffer
660 :header
'muse-journal-rss-header
661 :footer
'muse-journal-rss-footer
662 :date-format
'muse-journal-rss-date-format
663 :entry-template
'muse-journal-rss-entry-template
664 :base-url
'muse-journal-rss-base-url
665 :summarize
'muse-journal-rss-summarize-entries
))
667 (provide 'muse-journal
)
669 ;;; muse-journal.el ends here