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., 59 Temple Place - Suite 330, Boston,
20 ;; MA 02111-1307, 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:
42 ;; <div id="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 id="entry-body">
49 ;; <div id="entry-head">
50 ;; <div id="entry-date">
51 ;; <span class="date">March 17, 2004</span>
53 ;; <div id="entry-title">
54 ;; <h2>Title of entry</h2>
57 ;; <div id="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 match 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 formats to use for journal entries."
101 :group
'muse-journal
)
103 (defcustom muse-journal-html-heading-regexp
104 (concat "^<h2[^>]*>" muse-journal-heading-regexp
"</h2>$")
105 "A regexp that match a journal heading.
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
113 <a name=\"%anchor%\" style=\"text-decoration: none\"> </a>
114 <div id=\"entry-body\">
115 <div id=\"entry-head\">
116 <div id=\"entry-date\">
117 <span class=\"date\">%date%</span>
119 <div id=\"entry-title\">
123 <div id=\"entry-text\">
124 <div id=\"entry-qotd\">
133 :group
'muse-journal
)
135 (defcustom muse-journal-latex-section
136 "\\section*{%title% \\hfill {\\normalsize %date%}}
137 \\addcontentsline{toc}{chapter}{%title%}"
140 :group
'muse-journal
)
142 (defcustom muse-journal-latex-subsection
143 "\\subsection*{%title%}
144 \\addcontentsline{toc}{section}{%title%}"
147 :group
'muse-journal
)
149 (defcustom muse-journal-latex-markup-tags
150 '(("qotd" t nil muse-journal-latex-qotd-tag
))
151 "See `muse-publish-markup-tags' for more info."
152 :type
'(repeat (list (string :tag
"Markup tag")
153 (boolean :tag
"Expect closing tag" :value t
)
154 (boolean :tag
"Parse attributes" :value nil
)
156 :group
'muse-journal
)
158 (defun muse-journal-generate-pages ()
159 (let ((output-dir (muse-style-element :path
)))
160 (goto-char (point-min))
161 (while (re-search-forward muse-journal-heading-regexp nil t
)
162 (let* ((date (match-string 1))
163 (category (match-string 1))
164 (category-file (concat output-dir category
"/index.html"))
165 (heading (match-string 1)))
168 (defcustom muse-journal-rdf-extension
".rdf"
171 :group
'muse-journal
)
173 (defcustom muse-journal-rdf-base-url
""
174 "The base URL of the website reference by the RDF file."
176 :group
'muse-journal
)
178 (defcustom muse-journal-rdf-header
179 "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"
180 xmlns=\"http://purl.org/rss/1.0/\"
181 xmlns:dc=\"http://purl.org/dc/elements/1.1/\">
182 <channel rdf:about=\"<lisp>(concat (muse-style-element :base-url)
183 (muse-publish-output-name))</lisp>\">
184 <title><lisp>(muse-publishing-directive \"title\")</lisp></title>
185 <link><lisp>(concat (muse-style-element :base-url)
186 (concat (muse-page-name)
187 muse-html-extension))</lisp></link>
188 <description><lisp>(muse-publishing-directive \"desc\")</lisp></description>
191 <rdf:li resource=\"<lisp>
192 (concat (muse-style-element :base-url)
193 (concat (muse-page-name)
194 muse-html-extension))</lisp>\"/>
199 :type
'(choice string file
)
200 :group
'muse-journal
)
202 (defcustom muse-journal-rdf-footer
205 :type
'(choice string file
)
206 :group
'muse-journal
)
208 (defcustom muse-journal-rdf-date-format
212 :group
'muse-journal
)
214 (defcustom muse-journal-rdf-entry-template
215 " <item rdf:about=\"%link%#%anchor%\">
216 <title>%title%</title>
220 <link>%link%#%anchor%</link>
221 <dc:date>%date%</dc:date>
222 <dc:creator>%maintainer%</dc:creator>
226 :group
'muse-journal
)
228 (defcustom muse-journal-rdf-summarize-entries t
229 "If non-nil, include only summaries in the RDF file, not the full data."
231 :group
'muse-journal
)
233 (defcustom muse-journal-rss-extension
".xml"
236 :group
'muse-journal
)
238 (defcustom muse-journal-rss-base-url
""
239 "The base URL of the website reference by the RSS file."
241 :group
'muse-journal
)
243 (defcustom muse-journal-rss-header
244 "<\?xml version=\"1.0\" encoding=\"iso-8859-1\"\?>
245 <rss version=\"2.0\">
247 <title><lisp>(muse-publishing-directive \"title\")</lisp></title>
248 <link><lisp>(concat (muse-style-element :base-url)
249 (concat (muse-page-name)
250 muse-html-extension))</lisp></link>
251 <description><lisp>(muse-publishing-directive \"desc\")</lisp></description>
252 <language>en-us</language>
253 <generator>Emacs Muse</generator>"
255 :type
'(choice string file
)
256 :group
'muse-journal
)
258 (defcustom muse-journal-rss-footer
262 :type
'(choice string file
)
263 :group
'muse-journal
)
265 (defcustom muse-journal-rss-date-format
266 "%a, %d %b %Y %H:%M:%S %Z"
269 :group
'muse-journal
)
271 (defcustom muse-journal-rss-entry-template
273 <title>%title%</title>
274 <link>%link%#%anchor%</link>
275 <description>%desc%</description>
276 <author><lisp>(muse-publishing-directive \"author\")</lisp></author>
277 <pubDate>%date%</pubDate>
278 <guid>%link%#%anchor%</guid>
283 :group
'muse-journal
)
285 (defcustom muse-journal-rss-enclosure-types-alist
286 '(("mp3" .
"audio/mpeg"))
288 :type
'(alist :key-type string
:value-type string
)
289 :group
'muse-journal
)
291 (defcustom muse-journal-rss-summarize-entries nil
292 "If non-nil, include only summaries in the RSS file, not the full data."
294 :group
'muse-journal
)
296 (defcustom muse-journal-rss-markup-regexps
297 '((10000 muse-link-regexp
0 "\\2"))
298 "List of markup rules for publishing a Muse journal page to RDF.
299 For more on the structure of this list, see `muse-publish-markup-regexps'."
300 :type
'(repeat (choice
301 (list :tag
"Markup rule"
303 (choice regexp symbol
)
305 (choice string function symbol
))
307 :group
'muse-journal
)
309 (defcustom muse-journal-rss-markup-functions
313 "An alist of style types to custom functions for that kind of text.
314 For more on the structure of this list, see
315 `muse-publish-markup-functions'."
316 :type
'(alist :key-type symbol
:value-type function
)
317 :group
'muse-journal
)
319 (defun muse-journal-anchorize-title (title)
321 (if (string-match "(" title
)
322 (setq title
(substring title
0 (match-beginning 0))))
323 (if (string-match "<[^>]+>" title
)
324 (setq title
(replace-match "" nil nil title
)))
325 (downcase (replace-regexp-in-string "[^a-zA-Z0-9_]" ""
328 (defun muse-journal-sort-entries (&optional direction
)
334 (if (re-search-forward "^\\* [0-9]+" nil t
)
335 (goto-char (match-beginning 0))
336 (goto-char (point-max)))))
339 (if (re-search-forward "^\\* [0-9]+" nil t
)
340 (goto-char (1- (match-beginning 0)))
341 (goto-char (point-max)))))
349 (defun muse-journal-html-munge-buffer ()
350 (goto-char (point-min))
351 (let ((heading-regexp muse-journal-html-heading-regexp
)
352 (inhibit-read-only t
))
353 (while (re-search-forward heading-regexp nil t
)
354 (let* ((date (match-string 1))
356 (title (match-string 2))
359 (delete-region (match-beginning 0) (match-end 0))
362 (while (string-match "\\(^<[^>]+>\\|<[^>]+>$\\)" clean-title
)
363 (setq clean-title
(replace-match "" nil nil clean-title
)))))
367 (concat "\\`\\([1-9][0-9][0-9][0-9]\\)[./]?"
368 "\\([0-1][0-9]\\)[./]?\\([0-3][0-9]\\)") date
))
372 (string-to-int (match-string 3 date
))
373 (string-to-int (match-string 2 date
))
374 (string-to-int (match-string 1 date
))
376 date
(concat (format-time-string
377 muse-journal-date-format datestamp
)
378 (substring date
(match-end 0))))))
381 (point) (if (re-search-forward
382 (concat "\\(^<hr>$\\|"
383 heading-regexp
"\\)") nil t
)
386 (goto-char (point-max))
387 (while (and (not (bobp))
388 (eq ?\
(char-syntax (char-before))))
390 (goto-char (point-min))
391 (while (and (not (eobp))
392 (eq ?\
(char-syntax (char-after))))
395 (when (search-forward "<qotd>" nil t
)
396 (let ((tag-beg (match-beginning 0))
398 (re-search-forward "</qotd>\n*")
399 (setq qotd
(buffer-substring-no-properties
400 beg
(match-beginning 0)))
401 (delete-region tag-beg
(match-end 0)))))
402 (setq text
(buffer-string))
403 (delete-region (point-min) (point-max))
404 (let ((entry muse-journal-html-entry-template
))
405 (while (string-match "%date%" entry
)
406 (setq entry
(replace-match (or date
"") nil t entry
)))
407 (while (string-match "%title%" entry
)
408 (setq entry
(replace-match (or title
" ") nil t entry
)))
409 (while (string-match "%anchor%" entry
)
410 (setq entry
(replace-match
411 (muse-journal-anchorize-title
412 (or clean-title orig-date
))
414 (while (string-match "%qotd%" entry
)
415 (setq entry
(replace-match (or qotd
"") nil t entry
)))
416 (while (string-match "%text%" entry
)
417 (setq entry
(replace-match text nil t entry
)))
420 (goto-char (point-min))
421 (search-forward "<div id=\"entry-qotd\">")
422 (let ((beg (match-beginning 0)))
423 (re-search-forward "</div>\n*")
424 (delete-region beg
(point))))))))))
426 (defun muse-journal-latex-munge-buffer ()
427 (goto-char (point-min))
428 (let ((heading-regexp
429 (concat "^" (regexp-quote (muse-markup-text 'section
))
430 muse-journal-heading-regexp
431 (regexp-quote (muse-markup-text 'section-end
)) "$"))
432 (inhibit-read-only t
))
433 (when (re-search-forward heading-regexp nil t
)
434 (goto-char (match-beginning 0))
438 (if (re-search-forward heading-regexp nil t
)
439 (goto-char (match-beginning 0))
440 (goto-char (point-max)))))
443 (if (re-search-forward heading-regexp nil t
)
444 (goto-char (1- (match-beginning 0)))
445 (goto-char (point-max)))))
452 (while (re-search-forward heading-regexp nil t
)
453 (let ((date (match-string 1))
454 (title (match-string 2))
459 (concat "\\([1-9][0-9][0-9][0-9]\\)[./]?"
460 "\\([0-1][0-9]\\)[./]?\\([0-3][0-9]\\)") date
))
461 (setq date
(encode-time
463 (string-to-int (match-string 3 date
))
464 (string-to-int (match-string 2 date
))
465 (string-to-int (match-string 1 date
))
467 date
(format-time-string
468 muse-journal-date-format date
))))
470 (setq section muse-journal-latex-section
)
471 (while (string-match "%title%" section
)
472 (setq section
(replace-match (or title
"Untitled")
474 (while (string-match "%date%" section
)
475 (setq section
(replace-match (or date
"") nil t section
))))
476 (replace-match section nil t
))))
477 (goto-char (point-min))
478 (let ((subheading-regexp
479 (concat "^" (regexp-quote (muse-markup-text 'subsection
))
481 (regexp-quote (muse-markup-text 'subsection-end
)) "$"))
482 (inhibit-read-only t
))
483 (while (re-search-forward subheading-regexp nil t
)
484 (let ((subsection muse-journal-latex-subsection
))
486 (let ((title (match-string 1)))
487 (while (string-match "%title%" subsection
)
488 (setq subsection
(replace-match title nil t subsection
)))))
489 (replace-match subsection nil t
)))))
491 (defun muse-journal-latex-qotd-tag (beg end
)
493 (insert (muse-markup-text 'begin-quote
))
495 (insert (muse-markup-text 'end-quote
)))
497 (defun muse-journal-rss-munge-buffer ()
498 (goto-char (point-min))
499 (let ((heading-regexp (concat "^\\* " muse-journal-heading-regexp
"$"))
500 (inhibit-read-only t
))
501 (while (re-search-forward heading-regexp nil t
)
502 (let* ((date (match-string 1))
504 (title (match-string 2))
508 (if (string-match muse-link-regexp title
)
509 (setq enclosure
(match-string 1 title
)
510 title
(match-string 2 title
)))))
514 (concat "\\([1-9][0-9][0-9][0-9]\\)[./]?"
515 "\\([0-1][0-9]\\)[./]?\\([0-3][0-9]\\)") date
))
516 (setq date
(encode-time 0 0 0
517 (string-to-int (match-string 3 date
))
518 (string-to-int (match-string 2 date
))
519 (string-to-int (match-string 1 date
))
521 date
(format-time-string
522 (muse-style-element :date-format
) date
))))
526 (if (re-search-forward heading-regexp nil t
)
528 (if (re-search-forward "^Footnotes:" nil t
)
531 (goto-char (point-min))
532 (delete-region (point) (line-end-position))
533 (re-search-forward "</qotd>\n+" nil t
)
534 (while (and (char-after)
535 (eq ?\
(char-syntax (char-after))))
538 (if (muse-style-element :summarize
)
541 (setq desc
(concat (buffer-substring beg
(point)) "...")))
543 (muse-publish-markup-buffer "rss-entry" "html")
544 (goto-char (point-min))
545 (re-search-forward "Page published by Emacs Muse")
546 (goto-char (line-end-position))
548 (re-search-forward "Page published by Emacs Muse")
549 (goto-char (line-beginning-position))
550 (setq desc
(concat "<![CDATA[" (buffer-substring beg
(point))
552 (delete-region (point-min) (point-max))
553 (let ((entry (muse-style-element :entry-template
)))
554 (while (string-match "%date%" entry
)
555 (setq entry
(replace-match (or date
"") nil t entry
)))
556 (while (string-match "%title%" entry
)
557 (setq entry
(replace-match (or title
"Untitled") nil t entry
)))
558 (while (string-match "%desc%" entry
)
559 (setq entry
(replace-match desc nil t entry
)))
560 (while (string-match "%enclosure%" entry
)
568 "<enclosure url=\"%s\" %stype=\"%s\"/>"
569 (if (string-match "//" enclosure
)
571 (concat (muse-style-element :base-url
)
574 (expand-file-name enclosure
575 (muse-style-element :path
))))
576 (if (file-readable-p file
)
577 (format "length=\"%d\" "
578 (nth 7 (file-attributes file
)))
580 (if (string-match "\\.\\([^.]+\\)$" enclosure
)
581 (let* ((ext (match-string 1 enclosure
))
583 (assoc ext muse-journal-rss-enclosure-types-alist
)))
586 "application/octet-stream"))))))
588 (while (string-match "%link%" entry
)
589 (setq entry
(replace-match
590 (concat (muse-style-element :base-url
)
591 (concat (muse-page-name)
592 muse-html-extension
))
594 (while (string-match "%anchor%" entry
)
595 (setq entry
(replace-match
596 (muse-journal-anchorize-title (or title orig-date
))
598 (while (string-match "%maintainer%" entry
)
599 (setq entry
(replace-match
600 (or (muse-style-element :maintainer
)
601 (concat "webmaster@" (system-name)))
605 (delete-region (point) (point-max)))))
607 (unless (assoc "journal-html" muse-publishing-styles
)
608 (muse-derive-style "journal-html" "html"
609 :before-end
'muse-journal-html-munge-buffer
)
611 (muse-derive-style "journal-xhtml" "xhtml"
612 :before-end
'muse-journal-html-munge-buffer
)
614 (muse-derive-style "journal-latex" "latex"
615 :tags
'muse-journal-latex-markup-tags
616 :before-end
'muse-journal-latex-munge-buffer
)
618 (muse-derive-style "journal-pdf" "pdf"
619 :tags
'muse-journal-latex-markup-tags
620 :before-end
'muse-journal-latex-munge-buffer
)
622 (muse-derive-style "journal-book-latex" "book-latex"
624 :tags
'muse-journal-latex-markup-tags
625 :before-end
'muse-journal-latex-munge-buffer
)
627 (muse-derive-style "journal-book-pdf" "book-pdf"
629 :tags
'muse-journal-latex-markup-tags
630 :before-end
'muse-journal-latex-munge-buffer
)
632 (muse-define-style "journal-rdf"
633 :suffix
'muse-journal-rdf-extension
634 :regexps
'muse-journal-rss-markup-regexps
635 :functions
'muse-journal-rss-markup-functions
636 :before
'muse-journal-rss-munge-buffer
637 :header
'muse-journal-rdf-header
638 :footer
'muse-journal-rdf-footer
639 :date-format
'muse-journal-rdf-date-format
640 :entry-template
'muse-journal-rdf-entry-template
641 :base-url
'muse-journal-rdf-base-url
642 :summarize
'muse-journal-rdf-summarize-entries
)
644 (muse-define-style "journal-rss"
645 :suffix
'muse-journal-rss-extension
646 :regexps
'muse-journal-rss-markup-regexps
647 :functions
'muse-journal-rss-markup-functions
648 :before
'muse-journal-rss-munge-buffer
649 :header
'muse-journal-rss-header
650 :footer
'muse-journal-rss-footer
651 :date-format
'muse-journal-rss-date-format
652 :entry-template
'muse-journal-rss-entry-template
653 :base-url
'muse-journal-rss-base-url
654 :summarize
'muse-journal-rss-summarize-entries
))
656 (provide 'muse-journal
)
658 ;;; muse-journal.el ends here