po.scm 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2019 Julien Lepiller <julien@lepiller.eu>
  3. ;;; Copyright © 2020 Ludovic Courtès <ludo@gnu.org>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. (define-module (guix build po)
  20. #:use-module (ice-9 match)
  21. #:use-module (ice-9 peg)
  22. #:use-module (ice-9 textual-ports)
  23. #:export (read-po-file))
  24. ;; A small parser for po files
  25. (define-peg-pattern po-file body (* (or comment entry whitespace)))
  26. (define-peg-pattern whitespace body (or " " "\t" "\n"))
  27. (define-peg-pattern comment-chr body (range #\space #\頋))
  28. (define-peg-pattern comment none (and "#" (* comment-chr) "\n"))
  29. (define-peg-pattern entry all
  30. (and (ignore (* whitespace)) (ignore "msgid ") msgid
  31. (ignore (* whitespace)) (ignore "msgstr ") msgstr))
  32. (define-peg-pattern escape body (or "\\\\" "\\\"" "\\n"))
  33. (define-peg-pattern str-chr body (or " " "!" (and (ignore "\\") "\"")
  34. "\\n" (and (ignore "\\") "\\")
  35. (range #\# #\頋)))
  36. (define-peg-pattern msgid all content)
  37. (define-peg-pattern msgstr all content)
  38. (define-peg-pattern content body
  39. (and (ignore "\"") (* str-chr) (ignore "\"")
  40. (? (and (ignore (* whitespace)) content))))
  41. (define (interpret-newline-escape str)
  42. "Replace '\\n' sequences in STR with a newline character."
  43. (let loop ((str str)
  44. (result '()))
  45. (match (string-contains str "\\n")
  46. (#f (string-concatenate-reverse (cons str result)))
  47. (index
  48. (let ((prefix (string-take str index)))
  49. (loop (string-drop str (+ 2 index))
  50. (append (list "\n" prefix) result)))))))
  51. (define (parse-tree->assoc parse-tree)
  52. "Converts a po PARSE-TREE to an association list."
  53. (match parse-tree
  54. (() '())
  55. ((entry . parse-tree)
  56. (match entry
  57. ((? string? entry)
  58. (parse-tree->assoc parse-tree))
  59. ;; empty msgid
  60. (('entry ('msgid ('msgstr msgstr)))
  61. (parse-tree->assoc parse-tree))
  62. ;; empty msgstr
  63. (('entry ('msgid msgid) 'msgstr)
  64. (parse-tree->assoc parse-tree))
  65. (('entry ('msgid msgid) ('msgstr msgstr))
  66. (acons (interpret-newline-escape msgid)
  67. (interpret-newline-escape msgstr)
  68. (parse-tree->assoc parse-tree)))))))
  69. (define (read-po-file port)
  70. "Read a .po file from PORT and return an alist of msgid and msgstr."
  71. (let ((tree (peg:tree (match-pattern
  72. po-file
  73. (get-string-all port)))))
  74. (parse-tree->assoc tree)))