(**************************************************************************) (* ocaml-gettext: a library to translate messages *) (* *) (* Copyright (C) 2003-2008 Sylvain Le Gall *) (* *) (* This library is free software; you can redistribute it and/or *) (* modify it under the terms of the GNU Lesser General Public *) (* License as published by the Free Software Foundation; either *) (* version 2.1 of the License, or (at your option) any later version; *) (* with the OCaml static compilation exception. *) (* *) (* This library 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 *) (* Lesser General Public License for more details. *) (* *) (* You should have received a copy of the GNU Lesser General Public *) (* License along with this library; if not, write to the Free Software *) (* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *) (* USA *) (**************************************************************************) (** Ocaml-gettext tools. @author Sylvain Le Gall *) (** Helper program to : - extract translatable strings from OCaml source, - compile PO file, - install MO file, - merge POT and PO file. *) open GettextTypes;; open GettextCategory;; open GettextUtils;; open FilePath.DefaultPath;; IFDEF CAMOMILE THEN module OcamlGettextRealize = GettextCamomile.Open ELSE IFDEF STUB THEN module OcamlGettextRealize = GettextStub.Native ELSE module OcamlGettextRealize = GettextDummy.Dummy ENDIF ENDIF ;; module OcamlGettext = Gettext.Program ( struct let textdomain = "ocaml-gettext" let codeset = None let dir = None let dependencies = Gettext.init end ) ( OcamlGettextRealize ) ;; type action = Extract | Compile | Install | Uninstall | Merge | Version | VersionShort ;; type t = { action_option : action option; extract_command : string; extract_default_option : string; extract_filename_options : (string * string) list; extract_pot : string; compile_output_file_option : string option; install_language_option : string option; install_category : GettextCategory.category; install_textdomain_option : string option; install_destdir : string; uninstall_language_option : string option; uninstall_category : GettextCategory.category; uninstall_textdomain_option : string option; uninstall_orgdir : string; merge_filename_pot : string; merge_backup_extension : string; input_files : string list; strict : bool; } ;; exception ActionRequired;; exception InstallUninstallTooManyFilename;; exception CompileTooManyFilename;; let string_of_exception exc = let s_ x = OcamlGettext.s_ x in match exc with ActionRequired -> (s_ "You must specify one action.") | InstallUninstallTooManyFilename -> (s_ "You cannot specify at the same time a language, a textdomain and provide more than one file to install/uninstall : all files will have the same destination filename.") | CompileTooManyFilename -> (s_ "You cannot specify a output filename and more than one filename : all the compiled file will have the same output filename") | _ -> Gettext.string_of_exception exc ;; let do_extract t = let real_lst = let rec extract_potfiles accu lst = match lst with str :: lst when str = "POTFILES" -> let chn = open_in str in let new_accu = let rec extract_potfiles_aux accu = try let new_filename = input_line chn in extract_potfiles_aux (new_filename :: accu) with End_of_file -> accu in extract_potfiles_aux accu in close_in chn; extract_potfiles new_accu lst | str :: lst -> extract_potfiles (str :: accu) lst | [] -> List.rev accu in extract_potfiles [] t.input_files in let map_filename_options = List.fold_left ( fun map (fl,options) -> MapString.add fl options map ) MapString.empty t.extract_filename_options in GettextCompile.extract t.extract_command t.extract_default_option map_filename_options real_lst t.extract_pot ;; let do_compile t = match (t.compile_output_file_option,t.input_files) with Some fl_mo, [fl_po] -> GettextCompile.compile fl_po fl_mo | None, lst -> let fl_mo_of_fl_po fl_po = (* BUG: should use add_extension *) (chop_extension fl_po)^".mo" in List.iter ( fun fl_po -> GettextCompile.compile fl_po (fl_mo_of_fl_po fl_po) ) lst | Some _, [] -> () | Some _, lst -> raise CompileTooManyFilename ;; let guess_language_textdomain (language_option,textdomain_option) lst = (* Rules for guessing language : language[.textdomain].mo *) match (language_option,textdomain_option,lst) with Some language, Some textdomain, [fl_mo] -> [(language,textdomain,fl_mo)] | Some _, Some _, [] -> [] | Some _, Some _, lst -> raise InstallUninstallTooManyFilename | Some language, None, lst -> List.map (fun fl_mo -> (language,(chop_extension fl_mo),fl_mo)) lst | None, Some textdomain, lst -> List.map (fun fl_mo -> ((chop_extension fl_mo),textdomain,fl_mo)) lst | None, None, lst -> List.map (fun fl_mo -> (* BUG: should be able to have get_extension working *) (* let str_reduce = chop_extension fl_mo in * (((chop_extension str_reduce), (get_extension str_reduce)),fl_mo)*) raise (Failure "FilePath suffers from a default with the handling of chop/get_extension. This bug should disappears with newer version of ocaml-fileutils") ) lst ;; let do_install t = let install (language,textdomain,fl_mo) = GettextCompile.install t.strict t.install_destdir language t.install_category textdomain fl_mo in List.iter install ( guess_language_textdomain (t.install_language_option,t.install_textdomain_option) t.input_files ) ;; let do_uninstall t = let uninstall (language,textdomain,_) = GettextCompile.uninstall t.uninstall_orgdir language t.uninstall_category textdomain in List.iter uninstall (guess_language_textdomain (t.uninstall_language_option,t.uninstall_textdomain_option) t.input_files) ;; let do_merge t = GettextCompile.merge t.merge_filename_pot t.input_files t.merge_backup_extension ;; let do_action t = match t.action_option with Some Extract -> do_extract t | Some Compile -> do_compile t | Some Install -> do_install t | Some Uninstall -> do_uninstall t | Some Merge -> do_merge t | Some Version -> ( let (_,gettext_copyright) = OcamlGettext.init in print_string gettext_copyright; print_newline () ) | Some VersionShort -> ( print_string GettextConfig.version; print_newline () ) | None -> raise ActionRequired ;; let () = let spf x = Printf.sprintf x in let f_ x = OcamlGettext.f_ x in let s_ x = OcamlGettext.s_ x in let t = ref { action_option = None; extract_command = "ocaml-xgettext"; extract_default_option = "-I +camlp4 pa_o.cmo"; extract_filename_options = []; extract_pot = "messages.pot"; compile_output_file_option = None; install_language_option = None; install_category = LC_MESSAGES; install_textdomain_option = None; install_destdir = GettextConfig.default_dir; uninstall_language_option = None; uninstall_category = LC_MESSAGES; uninstall_textdomain_option = None; uninstall_orgdir = GettextConfig.default_dir; merge_filename_pot = "messages.pot"; merge_backup_extension = "bak"; input_files = []; strict = false; } in let actions = [ "extract", Extract; "compile", Compile; "install", Install; "uninstall", Uninstall; "merge", Merge ] in let (gettext_args,gettext_copyright) = OcamlGettext.init in let args = Arg.align ( [ ( "--action", Arg.Symbol ( (List.map fst actions), (fun symbol -> try t := { !t with action_option = Some (List.assoc symbol actions) } with Not_found -> raise (Arg.Bad (spf (f_ "Invalid action: %s.") symbol)) ) ), ( (s_ "Action to execute. Default: none.") ) ); ( "--extract-command", Arg.String ( fun cmd -> t := { !t with extract_command = cmd } ), ( spf (f_ "cmd Command to extract translatable strings from an OCaml source file. Default: %s.") !t.extract_command ) ); ( "--extract-default-option", Arg.String ( fun default_option -> t := { !t with extract_default_option = default_option } ), ( spf (f_ "options Default option used when extracting translatable strings. Default: %S.") !t.extract_default_option ) ); ( "--extract-filename-option", Arg.Tuple ( let filename = ref "" in [ Arg.String ( fun str -> filename := str ); Arg.String ( fun options -> t := { !t with extract_filename_options = (!filename,options) :: !t.extract_filename_options } ) ] ), ( spf (f_ "filename options Per filename option used when extracting strings from the specified filename. Default: %s.") (string_of_list ( List.map ( fun (str1,str2) -> spf "(%s,%s)" str1 str2 ) !t.extract_filename_options ) ) ) ); ( "--extract-pot", ( Arg.String ( fun str -> t := { !t with extract_pot = str } ) ), spf (f_ "filename POT file to write when extracting translatable strings. Default: %s.") !t.extract_pot ); ( "--compile-output", ( Arg.String ( fun str -> t := { !t with compile_output_file_option = Some str } ) ), (s_ "filename MO file to write when compiling a PO file. Default: name of the PO file with \".mo\" extension.") ); ( "--install-language", ( Arg.String ( fun str -> t := { !t with install_language_option = Some str } ) ), (s_ "language Language to use when installing a MO file. Default: try to guess it from the name of the MO file.") ); ( "--install-category", ( Arg.String ( fun str -> t := { !t with install_category = GettextCategory.category_of_string str } ) ), spf (f_ "category Category to use when installing a MO file. Default: %s.") (GettextCategory.string_of_category !t.install_category) ); ( "--install-textdomain", ( Arg.String ( fun str -> t := { !t with install_textdomain_option = Some str } ) ), (s_ "textdomain Textdomain to use when installing a MO file. Default: try to guess it from the name of the MO file.") ); ( "--install-destdir", ( Arg.String ( fun str -> t := { !t with install_destdir = str } ) ), spf (f_ "dirname Base dir used when installing a MO file. Default: %s.") !t.install_destdir ); ( "--strict", Arg.Unit (fun () -> t := {!t with strict = true}), spf (f_ " Additional check are errors during install. Default: %b.") !t.strict ); ( "--uninstall-language", ( Arg.String ( fun str -> t := { !t with uninstall_language_option = Some str } ) ), (s_ "language Language to use when uninstalling a MO file. Default: try to guess it from the name of the MO file.") ); ( "--uninstall-category", ( Arg.String ( fun str -> t := { !t with uninstall_category = GettextCategory.category_of_string str } ) ), spf (f_ "category Category to use when uninstalling a MO file. Default: %s.") (GettextCategory.string_of_category !t.uninstall_category) ); ( "--uninstall-textdomain", ( Arg.String ( fun str -> t := { !t with uninstall_textdomain_option = Some str } ) ), (s_ "textdomain Textdomain to use when uninstalling a MO file. Default: try to guess it from the name of the MO file.") ); ( "--uninstall-orgdir", ( Arg.String ( fun str -> t := { !t with uninstall_orgdir = str } ) ), spf (f_ "dirname Base dir used when uninstalling a MO file. Default: %s.") !t.uninstall_orgdir ); ( "--merge-pot", ( Arg.String ( fun str -> t := { !t with merge_filename_pot = str } ) ), spf (f_ "filename POT file to use as a master for merging PO file. Default: %s.") !t.merge_filename_pot ); ( "--merge-backup-extension", ( Arg.String ( fun str -> t := { !t with merge_backup_extension = str } ) ), spf (f_ "extension Backup extension to use when moving PO file which have been merged. Default: %s.") !t.merge_backup_extension ); ( "--version", ( Arg.Unit ( fun () -> t := { !t with action_option = Some Version } ) ), (s_ " Returns version information on ocaml-gettext.") ); ( "--short-version", ( Arg.Unit ( fun () -> t := { !t with action_option = Some VersionShort } ) ), (s_ " Returns only the version string of ocaml-gettext.") ); ] @ gettext_args ) in let () = Arg.parse args ( fun str -> t := { !t with input_files = str :: !t.input_files } ) ( spf (f_ "%s Command: ocaml-gettext -action (%s) [options] When trying to guess language and textdomain from a MO file, the rules applied are: language.textdomain.mo Options:") gettext_copyright (String.concat "|" (List.map fst actions)) ) in try do_action !t with exc -> ( prerr_string (string_of_exception exc); prerr_newline (); prerr_string (s_ "An error occurs while processing."); prerr_newline (); exit 1 ) ;;