From c8294c3b71d691fcf9d39d55105f9d2f04bbce85 Mon Sep 17 00:00:00 2001 From: gemmaro Date: Wed, 7 Feb 2024 22:19:43 +0900 Subject: [PATCH] New module for Gemtext format --- NEWS | 3 + doc/po4a.7.pod | 7 ++ lib/Locale/Po4a/Gemtext.pm | 193 +++++++++++++++++++++++++++++++++++++ t/fmt-gemtext.t | 20 ++++ t/fmt/gemtext/basic.gmi | 21 ++++ t/fmt/gemtext/basic.norm | 21 ++++ t/fmt/gemtext/basic.po | 83 ++++++++++++++++ t/fmt/gemtext/basic.pot | 83 ++++++++++++++++ t/fmt/gemtext/basic.trans | 21 ++++ 9 files changed, 452 insertions(+) create mode 100644 lib/Locale/Po4a/Gemtext.pm create mode 100644 t/fmt-gemtext.t create mode 100644 t/fmt/gemtext/basic.gmi create mode 100644 t/fmt/gemtext/basic.norm create mode 100644 t/fmt/gemtext/basic.po create mode 100644 t/fmt/gemtext/basic.pot create mode 100644 t/fmt/gemtext/basic.trans diff --git a/NEWS b/NEWS index a5f292c5e..b3e6277b7 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,9 @@ __ __/ _ \|___ / | \ V /| |_| | / / | | \_/ \___(_)_/ |_| (not released yet) +Gemtext: + * New module (GitHub's #466) [gemmaro]. + ======================================================================= ___ _____ ___ __ __/ _ \|___ / _ \ diff --git a/doc/po4a.7.pod b/doc/po4a.7.pod index c50978864..c2baaf33b 100644 --- a/doc/po4a.7.pod +++ b/doc/po4a.7.pod @@ -210,6 +210,13 @@ requirements to become an official GNU project). The support for L in po4a is still at the beginning. Please report bugs and feature requests. +=item gemtext (very highly experimental parser) + +The native plain text format of the Gemini protocol. The extension +".gmi" is commonly used. Support for this module in po4a is still in +its infancy. If you find anything, please file a bug or feature +request. + =item Others supported formats Po4a can also handle some more rare or specialized formats, such as the diff --git a/lib/Locale/Po4a/Gemtext.pm b/lib/Locale/Po4a/Gemtext.pm new file mode 100644 index 000000000..7be810f82 --- /dev/null +++ b/lib/Locale/Po4a/Gemtext.pm @@ -0,0 +1,193 @@ +#!/usr/bin/env perl -w + +# Po4a::Gemtext.pm +# +# extract and translate translatable strings from a Gemtext documents +# +# This program 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 of the License, or +# (at your option) any later version. +# +# This program 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 should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +######################################################################## + +require Exporter; + +package Locale::Po4a::Gemtext; + +use 5.006; +use strict; +use warnings; + +use vars qw(@ISA @EXPORT @AUTOLOAD); +@ISA = qw(Locale::Po4a::TransTractor); +@EXPORT = qw(); + +use Locale::Po4a::TransTractor qw(process new); +use Locale::Po4a::Common; + +=encoding UTF-8 + +=head1 NAME + +Locale::Po4a::Gemtext - convert Gettext documents from/to PO files. + +=head1 DESCRIPTION + +The po4a (PO for anything) project goal is to ease translations (and more +interestingly, the maintenance of translations) using gettext tools on +areas where they were not expected like documentation. + +Locale::Po4a::Gemtext is a module to help the translation of Gemtext documents into +other [human] languages. + +=cut + +sub initialize { } + +sub parse { + my $self = shift; + + my ( $line, $ref ) = $self->shiftline(); + + while ( defined($line) ) { + chomp($line); + + $self->parse_heading( $line, $ref ) + or $self->parse_preformatted_text( $line, $ref ) + or $self->parse_list( $line, $ref ) + or $self->parse_quote( $line, $ref ) + or $self->parse_link( $line, $ref ) + or $self->pushline( $self->translate( $line, $ref, "paragraph" ) . "\n" ); + + ( $line, $ref ) = $self->shiftline(); + } +} + +sub parse_heading() { + my $self = shift; + my $line = shift; + my $ref = shift; + + $line =~ m/^(#{1,3}) *(.+)/ or return; + my $level = $1; + my $content = $2; + + $self->pushline( "$level " . $self->translate( $content, $ref, "heading $level" ) . "\n" ); + + return 1; +} + +sub parse_preformatted_text() { + my $self = shift; + my $line = shift; + my $ref = shift; + + $line =~ m/^(``` *)(.*)/ or return; + my $prefix = $1; + my $content = $2; + + my $toggle_line = $prefix; + $toggle_line .= $self->translate( $content, $ref, "alt text" ) if $content; + $self->pushline("$toggle_line\n"); + + my $paragraph; + ( $line, $ref ) = $self->shiftline(); + + while ( defined($line) ) { + chomp($line); + + if ( $line =~ m/^```/ ) { + $self->pushline( $self->translate( $paragraph, $ref, "preformatted text" ) . "\n" ); + $self->pushline("$line\n"); + + return 1; + } + + if ($paragraph) { + $paragraph .= "\n$line"; + } else { + $paragraph = $line; + } + + ( $line, $ref ) = $self->shiftline(); + } +} + +sub parse_list() { + my $self = shift; + my $line = shift; + my $ref = shift; + + $line =~ m/^\* (.+)/ or return; + my $content = $1; + + $self->pushline( "* " . $self->translate( $content, $ref, "list" ) . "\n" ); + + return 1; +} + +sub parse_quote() { + my $self = shift; + my $line = shift; + my $ref = shift; + + $line =~ m/^(> *)(.+)/ or return; + my $prefix = $1; + my $content = $2; + + $self->pushline( $prefix . $self->translate( $content, $ref, "quote" ) . "\n" ); + + return 1; +} + +sub parse_link() { + my $self = shift; + my $line = shift; + my $ref = shift; + + $line =~ m/^(=>[ \t]+[^ \t]+)(?:([ \t]+)(.*))?/ or return; + my $prefix = $1; + my $separator = $2; + my $content = $3; + + my $result = $prefix; + $result .= $separator . $self->translate( $content, $ref, "link" ) + if $content; + $self->pushline( $result . "\n" ); + + return 1; +} + +1; + +=head1 STATUS OF THIS MODULE + +Tested successfully on simple Gemtext files, such as the official Gemtext documentation. + +=head1 SEE ALSO + +L, L + +=head1 AUTHORS + + gemmaro + +=head1 COPYRIGHT AND LICENSE + + Copyright © 2024 gemmaro . + +This program is free software; you may redistribute it and/or modify it +under the terms of GPL v2.0 or later (see the COPYING file). + +=cut diff --git a/t/fmt-gemtext.t b/t/fmt-gemtext.t new file mode 100644 index 000000000..822a4a53a --- /dev/null +++ b/t/fmt-gemtext.t @@ -0,0 +1,20 @@ +# Gemtext module tester. + +######################### + +use strict; +use warnings; + +use lib q(t); +use Testhelper; + +my @tests; + +push @tests, + { + 'format' => 'gemtext', + 'input' => "fmt/gemtext/basic.gmi", + }; + +run_all_tests(@tests); +0; diff --git a/t/fmt/gemtext/basic.gmi b/t/fmt/gemtext/basic.gmi new file mode 100644 index 000000000..9850c8767 --- /dev/null +++ b/t/fmt/gemtext/basic.gmi @@ -0,0 +1,21 @@ +This is a normal text line. + +=> gemini://example.org An example link + +``` alt text +Here is a preformatted text. +``` + +Here are exactly two blank lines: + + +# Heading + +## Sub Heading + +### Sub sub heading + +* Item 1 +* Item 2 + +> This is a quote. diff --git a/t/fmt/gemtext/basic.norm b/t/fmt/gemtext/basic.norm new file mode 100644 index 000000000..9850c8767 --- /dev/null +++ b/t/fmt/gemtext/basic.norm @@ -0,0 +1,21 @@ +This is a normal text line. + +=> gemini://example.org An example link + +``` alt text +Here is a preformatted text. +``` + +Here are exactly two blank lines: + + +# Heading + +## Sub Heading + +### Sub sub heading + +* Item 1 +* Item 2 + +> This is a quote. diff --git a/t/fmt/gemtext/basic.po b/t/fmt/gemtext/basic.po new file mode 100644 index 000000000..f6950568e --- /dev/null +++ b/t/fmt/gemtext/basic.po @@ -0,0 +1,83 @@ +# SOME DESCRIPTIVE TITLE +# Copyright (C) YEAR Free Software Foundation, Inc. +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2024-02-07 22:02+0900\n" +"PO-Revision-Date: 2024-02-07 22:09+0900\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. type: paragraph +#: basic.gmi:1 +#, no-wrap +msgid "This is a normal text line." +msgstr "THIS IS A NORMAL TEXT LINE." + +#. type: link +#: basic.gmi:3 +#, no-wrap +msgid "An example link" +msgstr "AN EXAMPLE LINK" + +#. type: alt text +#: basic.gmi:5 +#, no-wrap +msgid "alt text" +msgstr "ALT TEXT" + +#. type: preformatted text +#: basic.gmi:7 +#, no-wrap +msgid "Here is a preformatted text." +msgstr "HERE IS A PREFORMATTED TEXT." + +#. type: paragraph +#: basic.gmi:9 +#, no-wrap +msgid "Here are exactly two blank lines:" +msgstr "HERE ARE EXACTLY TWO BLANK LINES:" + +#. type: heading # +#: basic.gmi:12 +#, no-wrap +msgid "Heading" +msgstr "HEADING" + +#. type: heading ## +#: basic.gmi:14 +#, no-wrap +msgid "Sub Heading" +msgstr "SUB HEADING" + +#. type: heading ### +#: basic.gmi:16 +#, no-wrap +msgid "Sub sub heading" +msgstr "SUB SUB HEADING" + +#. type: list +#: basic.gmi:18 +#, no-wrap +msgid "Item 1" +msgstr "ITEM 1" + +#. type: list +#: basic.gmi:19 +#, no-wrap +msgid "Item 2" +msgstr "ITEM 2" + +#. type: quote +#: basic.gmi:21 +#, no-wrap +msgid "This is a quote." +msgstr "THIS IS A QUOTE." diff --git a/t/fmt/gemtext/basic.pot b/t/fmt/gemtext/basic.pot new file mode 100644 index 000000000..a45197b30 --- /dev/null +++ b/t/fmt/gemtext/basic.pot @@ -0,0 +1,83 @@ +# SOME DESCRIPTIVE TITLE +# Copyright (C) YEAR Free Software Foundation, Inc. +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2024-02-07 22:02+0900\n" +"PO-Revision-Date: 2024-02-07 22:05+0900\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. type: paragraph +#: basic.gmi:1 +#, no-wrap +msgid "This is a normal text line." +msgstr "" + +#. type: link +#: basic.gmi:3 +#, no-wrap +msgid "An example link" +msgstr "" + +#. type: alt text +#: basic.gmi:5 +#, no-wrap +msgid "alt text" +msgstr "" + +#. type: preformatted text +#: basic.gmi:7 +#, no-wrap +msgid "Here is a preformatted text." +msgstr "" + +#. type: paragraph +#: basic.gmi:9 +#, no-wrap +msgid "Here are exactly two blank lines:" +msgstr "" + +#. type: heading # +#: basic.gmi:12 +#, no-wrap +msgid "Heading" +msgstr "" + +#. type: heading ## +#: basic.gmi:14 +#, no-wrap +msgid "Sub Heading" +msgstr "" + +#. type: heading ### +#: basic.gmi:16 +#, no-wrap +msgid "Sub sub heading" +msgstr "" + +#. type: list +#: basic.gmi:18 +#, no-wrap +msgid "Item 1" +msgstr "" + +#. type: list +#: basic.gmi:19 +#, no-wrap +msgid "Item 2" +msgstr "" + +#. type: quote +#: basic.gmi:21 +#, no-wrap +msgid "This is a quote." +msgstr "" diff --git a/t/fmt/gemtext/basic.trans b/t/fmt/gemtext/basic.trans new file mode 100644 index 000000000..77485a497 --- /dev/null +++ b/t/fmt/gemtext/basic.trans @@ -0,0 +1,21 @@ +THIS IS A NORMAL TEXT LINE. + +=> gemini://example.org AN EXAMPLE LINK + +``` ALT TEXT +HERE IS A PREFORMATTED TEXT. +``` + +HERE ARE EXACTLY TWO BLANK LINES: + + +# HEADING + +## SUB HEADING + +### SUB SUB HEADING + +* ITEM 1 +* ITEM 2 + +> THIS IS A QUOTE.