;;; mo-mode.el --- view gettext .mo message files

;; Copyright (C) 2006 Kevin Ryde
;;
;; mo-mode.el is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by the
;; Free Software Foundation; either version 2, or (at your option) any later
;; version.
;;
;; mo-mode.el is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
;; FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
;; more details.
;;
;; You can get a copy of the GNU General Public License online at
;; http://www.gnu.org/licenses/gpl.txt, or you should have one in the file
;; COPYING which comes with GNU Emacs and other GNU programs.  Failing that,
;; write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
;; Boston, MA 02110-1301 USA.

;;; Commentary:

;; mo-mode decodes and displays gettext style .mo and .gmo files using
;; msgunfmt from gettext.  There's no scope for editing and rewriting the
;; original, but that should be very rarely needed.

;;; Install:

;; Put mo-mode.el somewhere in your load-path and the following in your
;; .emacs
;;
;;     (autoload 'mo-mode "mo-mode" nil t)
;;     (modify-coding-system-alist 'file "\\.g?mo\\'" 'no-conversion)
;;     (add-to-list 'auto-mode-alist '("\\.g?mo\\'" . mo-mode))
;;

;;; History:

;; Version 1 - the first version.

;;; Emacsen:

;; Designed for Emacs 21 and 22.

;;; Code:

(defun mo-mode-charset-to-coding-system (charset)
  "Return an Emacs coding system for the given po file CHARSET.
CHARSET is a string name and is normally in libc style, like
nl_langinfo(CODESET) gives or iconv_open() accepts."

  ;; there's no easy way to re-use `po-find-file-coding-system', the code
  ;; there insists on doing insert-file-contents :-(
  ;; in any case want to work when that's not available (emacs 21 without
  ;; the emacs bits of gettext installed)

  (or
   ;; locale-charset-to-coding-system is pretty much designed exactly for
   ;; what we need, if it's available
   (and (fboundp 'locale-charset-to-coding-system) ;; emacs 22
        (locale-charset-to-coding-system charset))

   ;; po-content-type-charset-alist, if available
   (and (or (require 'po nil t)           ;; emacs 22
            (require 'po-compat nil t))   ;; standalone gettext
        (boundp 'po-content-type-charset-alist)
        (cdr (assoc (upcase charset) po-content-type-charset-alist)))

   ;; otherwise try the gnus mime-ish lookup, it can mung names suitably
   (and (require 'mm-util nil t)
        (let ((coding (mm-charset-to-coding-system charset)))
          ;; the return is 'ascii for for ascii or us-ascii, but that's not
          ;; a coding system, go undecided instead
          (if (and (eq coding 'ascii)
                   (not (coding-system-p coding)))
              (setq coding 'undecided))
          coding))))

;;;###autoload
(defun mo-mode ()
  "Decode and view gettext .mo or .gmo message files.
The raw file is put through msgunfmt to produce .po style text which is then
presented with `po-mode' if available, or `view-mode' if not.  The buffer is
read-only in either case because there's no support for saving any changes."
  (interactive)
  (setq buffer-read-only nil)

  ;; initial file read with 'no-conversion, so we're raw unibytes here;
  ;; pipe through msgunfmt likewise
  (let ((coding-system-for-read  'no-conversion)
        (coding-system-for-write 'no-conversion))
    (or (= 0 (call-process-region (point-min) (point-max) "msgunfmt" t t nil))
        (error "Error running msgunfmt")))

  ;; look at header charset spec and decode the bytes accordingly
  ;; the charset warning messages will be obscured by po-mode, and by
  ;; view-mode when run from view-file; the latter afflicts all mode setups,
  ;; not just us (eg. html-mode charset in emacs 22)
  (goto-char (point-min))
  (set-buffer-modified-p nil) ;; before throwing errors
  (if (not (re-search-forward
            "^\"Content-Type: text/plain;[ \t]*charset=\\(.*\\)\\\\n\""
            nil t))
      (message "MO Content-Type charset not found")

    (let* ((charset (match-string 1))
           (coding  (mo-mode-charset-to-coding-system charset)))
      (if (not (and coding (coding-system-p coding)))
          (message "MO Content-Type charset unknown: %s" charset)

        (decode-coding-region (point-min) (point-max) coding)
        (set-buffer-multibyte t)
        (setq buffer-file-coding-system coding))))

  ;; prevent editing since it can't be saved
  (set-buffer-modified-p nil)
  (setq buffer-read-only t)

  (if (require 'po-mode nil t) ;; from gettext
      (po-mode)
    (text-mode)
    (view-buffer (current-buffer) 'kill-buffer)))

(provide 'mo-mode)

;;; mo-mode.el ends here
